├── .editorconfig ├── LICENSE ├── README ├── README.md ├── autotheme ├── autotheme.lua ├── main3.lua └── themes │ ├── default │ └── theme.ini │ └── wide │ ├── bg.png │ └── theme.ini ├── cutscene ├── cutscene.lua └── main3.lua ├── decor ├── anim.png ├── color2rgb.lua ├── decor.lua └── main3.lua ├── extlib ├── extlib-ru.lua ├── extlib.lua ├── main3.lua └── morph │ └── lang-ru.lua ├── fading ├── fading.lua └── main3.lua ├── fonts ├── fonts.lua ├── main3.lua └── sans.ttf ├── keyboard ├── keyboard.lua └── main3.lua ├── link ├── link.lua └── main3.lua ├── protect ├── open │ ├── dec.lua.cpt │ └── enc.lua.cpt └── protect.lua ├── proxymenu ├── main3.lua └── proxymenu.lua ├── quake ├── main3.lua └── quake.lua ├── sfxr ├── main3.lua └── sfxr.lua ├── use ├── main3.lua └── use.lua └── xrefs ├── main3.lua └── xrefs.lua /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = tab 6 | end_of_line = lf 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 INSTEAD 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Project has moved to: https://github.com/instead-hub/metaparser 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # STEAD3 modules 2 | 3 | Modules for INSTEAD (STEAD3 API) 4 | 5 | ## autotheme 6 | 7 | Select theme with best aspect ratio from themes/ while first start. 8 | 9 | ## cutscene 10 | 11 | Simple cutscene module. 12 | 13 | ## decor 14 | 15 | Graphical decorators above or bellow text layer. 16 | 17 | ## fading 18 | 19 | Custom fading effects. 20 | 21 | ## fonts 22 | 23 | Custom fonts. 24 | 25 | ## keyboard 26 | 27 | On-screen keyboard module. 28 | 29 | ## link 30 | 31 | Link to web (copy to clipboard on click). 32 | 33 | ## proxymenu 34 | 35 | ZX Adventure game like menu. 36 | 37 | ## quake 38 | 39 | On-screen quake effect. 40 | 41 | ## sfxr 42 | 43 | Software sound effects synthesis. 44 | 45 | ## xrefs 46 | 47 | Keyboard driven gameplay w/o visual hyperlinks. 48 | 49 | ## extlib 50 | 51 | Extended library (metaparser elements). 52 | -------------------------------------------------------------------------------- /autotheme/autotheme.lua: -------------------------------------------------------------------------------- 1 | local function autodetect_theme() 2 | if not instead.screen_size then 3 | return 4 | end 5 | local f = io.open(instead.savepath().."/config.ini", "r") 6 | if f then 7 | f:close() 8 | return 9 | end 10 | f = io.open(instead.gamepath().."/themes/default/theme.ini", "r") 11 | if not f then 12 | return 13 | end 14 | f:close() 15 | 16 | local themes = {} 17 | local vertical = false 18 | for d in std.readdir(instead.gamepath().."/themes") do 19 | if d ~= '.' and d ~= '..' then 20 | local p = instead.gamepath().."/themes/" .. d 21 | local f = io.open(p.."/theme.ini", "r") 22 | if f then 23 | local w, h 24 | for l in f:lines() do 25 | if l:find("scr%.w[ \t]*=[ \t]*[0-9]+") then 26 | w = l:gsub("scr%.w[ \t]*=[ \t]*([0-9]+)", "%1") 27 | elseif l:find("scr%.h[ \t]*=[ \t]*[0-9]+") then 28 | h = l:gsub("scr%.h[ \t]*=[ \t]*([0-9]+)", "%1") 29 | end 30 | if w and h then break end 31 | end 32 | if w and h then 33 | w = tonumber(w) 34 | h = tonumber(h) 35 | local r = w / h 36 | if r < 1 then r = 1 / r end 37 | table.insert(themes, { nam = d, w = w, h = h, mobile = w < h, ratio = r }) 38 | vertical = vertical or (w < h) 39 | end 40 | f:close() 41 | end 42 | end 43 | end 44 | 45 | if #themes == 1 then 46 | return 47 | end 48 | local w, h = instead.screen_size() 49 | local r = w / h 50 | local mobile = w < h or PLATFORM == "ANDROID" or PLATFORM == "IOS" or PLATFORM == "S60" or PLATFORM == "WINRT" or PLATFORM == "WINCE" 51 | if w < h then 52 | r = 1 / r 53 | end 54 | local d = 1000 55 | local t = false 56 | for _, v in ipairs(themes) do 57 | local dd = math.abs(v.ratio - r) 58 | if dd < d then 59 | if mobile and (not vertical or v.mobile) then 60 | d = dd 61 | t = v 62 | elseif not mobile and not v.mobile then 63 | d = dd 64 | t = v 65 | end 66 | end 67 | end 68 | if not t or t.nam == 'default' then 69 | return 70 | end 71 | local name = instead.savepath().."/config.ini" 72 | local name_tmp = name .. '.tmp' 73 | local f = io.open(name_tmp, "w") 74 | if f then 75 | dprint("Autodetect theme: ", t.nam) 76 | f:write("theme = "..t.nam.."\n") 77 | f:close() 78 | std.os.remove(name) 79 | std.os.rename(name_tmp, name); 80 | instead.restart() 81 | end 82 | end 83 | 84 | std.mod_start(function() 85 | autodetect_theme() 86 | end, -100) 87 | -------------------------------------------------------------------------------- /autotheme/main3.lua: -------------------------------------------------------------------------------- 1 | require "autotheme" 2 | -------------------------------------------------------------------------------- /autotheme/themes/default/theme.ini: -------------------------------------------------------------------------------- 1 | scr.w = 800 2 | scr.h = 600 3 | -------------------------------------------------------------------------------- /autotheme/themes/wide/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instead-hub/stead3-modules/c97f6831fb3be47f9199cacf2677265cc0596bc9/autotheme/themes/wide/bg.png -------------------------------------------------------------------------------- /autotheme/themes/wide/theme.ini: -------------------------------------------------------------------------------- 1 | ; $Name:Wide$ 2 | ; $Name(ru):Широкая$ 3 | ; $Name(uk):Широка$ 4 | ; $Name(es):Amplio$ 5 | ; $Name(it):Ampio$ 6 | include = wide 7 | scr.w = 960 8 | scr.h = 540 9 | -------------------------------------------------------------------------------- /cutscene/cutscene.lua: -------------------------------------------------------------------------------- 1 | require "timer" 2 | 3 | std.cut_text = '>>>' 4 | 5 | local function get_token(txt, pos) 6 | if not pos then 7 | pos = 1 8 | end 9 | local s,e; 10 | e = pos 11 | while true do 12 | s, e = txt:find("[\\%[]", e); 13 | if not s then 14 | break 15 | end 16 | if txt:sub(s, s) == '\\' then 17 | e = e + 2 18 | else 19 | break 20 | end 21 | end 22 | local nest = 1 23 | local ss, ee 24 | ee = e 25 | while s do 26 | ss, ee = txt:find("[\\%[%]]", ee + 1); 27 | if ss then 28 | if txt:sub(ss, ss) == '\\' then 29 | ee = ee + 1 30 | elseif txt:sub(ss, ss) == '[' then 31 | nest = nest + 1 32 | else 33 | nest = nest - 1 34 | end 35 | if nest == 0 then 36 | return s, ee 37 | end 38 | else 39 | break 40 | end 41 | end 42 | return nil 43 | end 44 | 45 | local function parse_token(txt) 46 | local s, e, t 47 | t = txt:sub(2, -2) 48 | local c = t:gsub("^([a-zA-Z]+)[ \t]*.*$", "%1"); 49 | local a = t:gsub("^[^ \t]+[ \t]*(.*)$", "%1"); 50 | if a then a = a:gsub("[ \t]+$", "") end 51 | return c, a 52 | end 53 | 54 | cutscene = function(v) 55 | v.txt = v.decor 56 | 57 | if v.exit then 58 | error ("Do not use left in cutscene.", 2) 59 | end 60 | 61 | v.exit = function(s) 62 | timer:set(s.__timer); 63 | s:reset() 64 | end; 65 | 66 | if v.timer then 67 | error ("Do not use timer in cutscene.", 2) 68 | end 69 | 70 | v.timer = function(s) 71 | if not s.__to then 72 | if game.timer then 73 | return game:timer() 74 | end 75 | return false 76 | end 77 | if s.__fading ~= nil then 78 | instead.fading_value = s.__fading 79 | end 80 | s.__state = s.__state + 1 81 | timer:stop() 82 | s:step() 83 | end; 84 | 85 | if not v.pic then 86 | v.pic = function(s) 87 | return s.__pic 88 | end; 89 | end 90 | 91 | v.reset = function(s) 92 | s.__state = 1 93 | s.__code = 1 94 | s.__fading = nil 95 | s.__txt = nil 96 | s.__dsc = nil 97 | s.__pic = nil 98 | s.__to = nil 99 | s.__timer_fn = nil 100 | end 101 | 102 | v:reset() 103 | 104 | if v.enter then 105 | error ("Do not use entered in cutscene.", 2) 106 | end 107 | 108 | v.enter = function(self) 109 | self:reset() 110 | self.__timer = timer:get() 111 | self:step(); 112 | end; 113 | 114 | v.step = function(self) 115 | local s, e, c, a 116 | local n = v.__state 117 | local txt = '' 118 | local code = 0 119 | local out = '' 120 | if not self.__txt then 121 | if type(self.txt) == 'table' then 122 | local k,v 123 | for k,v in ipairs(self.txt) do 124 | if type(v) == 'function' then 125 | v = v() 126 | end 127 | txt = txt .. tostring(v) 128 | end 129 | else 130 | txt = stead.call(self, 'txt') 131 | end 132 | self.__txt = txt 133 | else 134 | txt = self.__txt 135 | end 136 | while n > 0 and txt do 137 | if not e then 138 | e = 1 139 | end 140 | local oe = e 141 | s, e = get_token(txt, e) 142 | if not s then 143 | c = nil 144 | out = out..txt:sub(oe) 145 | break 146 | end 147 | local strip = false 148 | local para = false 149 | c, a = parse_token(txt:sub(s, e)) 150 | if c == "pause" or c == "cut" or c == "fading" then 151 | n = n - 1 152 | strip = true 153 | para = true 154 | elseif c == "pic" then 155 | if a == '' then a = nil end 156 | self.__pic = a 157 | strip = true 158 | elseif c == "code" then 159 | code = code + 1 160 | if code >= self.__code then 161 | local f = stead.eval(a) 162 | if not f then 163 | error ("Wrong expression in cutscene: "..tostring(a)) 164 | end 165 | self.__code = self.__code + 1 166 | f() 167 | end 168 | strip = true 169 | elseif c == "walk" then 170 | if a and a ~= "" then 171 | return stead.walk(a) 172 | end 173 | elseif c == "cls" then 174 | out = '' 175 | strip = true 176 | end 177 | if strip then 178 | out = out..txt:sub(oe, s - 1) 179 | elseif c then 180 | out = out..txt:sub(oe, e) 181 | else 182 | out = out..txt:sub(oe) 183 | end 184 | if para and n == 1 and stead.cut_delim then 185 | out = out .. std.cut_delim 186 | end 187 | e = e + 1 188 | end 189 | if stead.cut_scroll then 190 | out = out..iface.anchor() 191 | end 192 | v.__dsc = out 193 | if c == 'pause' then 194 | if not a or a == "" then 195 | a = 1000 196 | end 197 | self.__to = tonumber(a) 198 | timer:set(self.__to) 199 | elseif c == 'cut' then 200 | self.__state = self.__state + 1 201 | if not a or a == "" then 202 | a = stead.cut_text 203 | end 204 | v.__dsc = v.__dsc .. "{#cut|"..a.."}"; 205 | elseif c == "fading" then 206 | if not a or a == "" then 207 | a = instead.fading_value 208 | end 209 | instead.need_fading(true) 210 | self.__fading = instead.fading_value 211 | instead.fading_value = a 212 | self.__to = 10 213 | timer:set(self.__to) 214 | end 215 | end 216 | v.decor = function(s) 217 | if s.__dsc then 218 | return s.__dsc 219 | end 220 | end 221 | if not v.obj then 222 | v.obj = {} 223 | end 224 | stead.table.insert(v.obj, 1, obj { nam = '#cut', act = function() here():step(); return true; end }) 225 | return room(v) 226 | end 227 | -------------------------------------------------------------------------------- /cutscene/main3.lua: -------------------------------------------------------------------------------- 1 | require "cutscene" 2 | require "fmt" 3 | 4 | cutscene { 5 | nam = 'main'; 6 | decor = function() 7 | pn (fmt.c("INSTEAD")) 8 | pn "[fading 16]" 9 | pn (fmt.c("http://instead.syscall.ru")) 10 | pn "[code print 'hello']" 11 | pn "2017" 12 | pn "[cut]" 13 | pn "Март" 14 | end; 15 | } 16 | -------------------------------------------------------------------------------- /decor/anim.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instead-hub/stead3-modules/c97f6831fb3be47f9199cacf2677265cc0596bc9/decor/anim.png -------------------------------------------------------------------------------- /decor/color2rgb.lua: -------------------------------------------------------------------------------- 1 | local colors = { 2 | aliceblue = 0xf0f8ff, 3 | antiquewhite = 0xfaebd7, 4 | aqua = 0x00ffff, 5 | aquamarine = 0x7fffd4, 6 | azure = 0xf0ffff, 7 | beige = 0xf5f5dc, 8 | bisque = 0xffe4c4, 9 | black = 0x000000, 10 | blanchedalmond = 0xffebcd, 11 | blue = 0x0000ff, 12 | blueviolet = 0x8a2be2, 13 | brown = 0xa52a2a, 14 | burlywood = 0xdeb887, 15 | cadetblue = 0x5f9ea0, 16 | chartreuse = 0x7fff00, 17 | chocolate = 0xd2691e, 18 | coral = 0xff7f50, 19 | cornflowerblue = 0x6495ed, 20 | cornsilk = 0xfff8dc, 21 | crimson = 0xdc143c, 22 | cyan = 0x00ffff, 23 | darkblue = 0x00008b, 24 | darkcyan = 0x008b8b, 25 | darkgoldenrod = 0xb8860b, 26 | darkgray = 0xa9a9a9, 27 | darkgrey = 0xa9a9a9, 28 | darkgreen = 0x006400, 29 | darkkhaki = 0xbdb76b, 30 | darkmagenta = 0x8b008b, 31 | darkolivegreen = 0x556b2f, 32 | darkorange = 0xff8c00, 33 | darkorchid = 0x9932cc, 34 | darkred = 0x8b0000, 35 | darksalmon = 0xe9967a, 36 | darkseagreen = 0x8fbc8f, 37 | darkslateblue = 0x483d8b, 38 | darkslategray = 0x2f4f4f, 39 | darkslategrey = 0x2f4f4f, 40 | darkturquoise = 0x00ced1, 41 | darkviolet = 0x9400d3, 42 | deeppink = 0xff1493, 43 | deepskyblue = 0x00bfff, 44 | dimgray = 0x696969, 45 | dimgrey = 0x696969, 46 | dodgerblue = 0x1e90ff, 47 | feldspar = 0xd19275, 48 | firebrick = 0xb22222, 49 | floralwhite = 0xfffaf0, 50 | forestgreen = 0x228b22, 51 | fuchsia = 0xff00ff, 52 | gainsboro = 0xdcdcdc, 53 | ghostwhite = 0xf8f8ff, 54 | gold = 0xffd700, 55 | goldenrod = 0xdaa520, 56 | gray = 0x808080, 57 | grey = 0x808080, 58 | green = 0x008000, 59 | greenyellow = 0xadff2f, 60 | honeydew = 0xf0fff0, 61 | hotpink = 0xff69b4, 62 | indianred = 0xcd5c5c, 63 | indigo = 0x4b0082, 64 | ivory = 0xfffff0, 65 | khaki = 0xf0e68c, 66 | lavender = 0xe6e6fa, 67 | lavenderblush = 0xfff0f5, 68 | lawngreen = 0x7cfc00, 69 | lemonchiffon = 0xfffacd, 70 | lightblue = 0xadd8e6, 71 | lightcoral = 0xf08080, 72 | lightcyan = 0xe0ffff, 73 | lightgoldenrodyellow = 0xfafad2, 74 | lightgray = 0xd3d3d3, 75 | lightgrey = 0xd3d3d3, 76 | lightgreen = 0x90ee90, 77 | lightpink = 0xffb6c1, 78 | lightsalmon = 0xffa07a, 79 | lightseagreen = 0x20b2aa, 80 | lightskyblue = 0x87cefa, 81 | lightslateblue = 0x8470ff, 82 | lightslategray = 0x778899, 83 | lightslategrey = 0x778899, 84 | lightsteelblue = 0xb0c4de, 85 | lightyellow = 0xffffe0, 86 | lime = 0x00ff00, 87 | limegreen = 0x32cd32, 88 | linen = 0xfaf0e6, 89 | magenta = 0xff00ff, 90 | maroon = 0x800000, 91 | mediumaquamarine = 0x66cdaa, 92 | mediumblue = 0x0000cd, 93 | mediumorchid = 0xba55d3, 94 | mediumpurple = 0x9370d8, 95 | mediumseagreen = 0x3cb371, 96 | mediumslateblue = 0x7b68ee, 97 | mediumspringgreen = 0x00fa9a, 98 | mediumturquoise = 0x48d1cc, 99 | mediumvioletred = 0xc71585, 100 | midnightblue = 0x191970, 101 | mintcream = 0xf5fffa, 102 | mistyrose = 0xffe4e1, 103 | moccasin = 0xffe4b5, 104 | navajowhite = 0xffdead, 105 | navy = 0x000080, 106 | oldlace = 0xfdf5e6, 107 | olive = 0x808000, 108 | olivedrab = 0x6b8e23, 109 | orange = 0xffa500, 110 | orangered = 0xff4500, 111 | orchid = 0xda70d6, 112 | palegoldenrod = 0xeee8aa, 113 | palegreen = 0x98fb98, 114 | paleturquoise = 0xafeeee, 115 | palevioletred = 0xd87093, 116 | papayawhip = 0xffefd5, 117 | peachpuff = 0xffdab9, 118 | peru = 0xcd853f, 119 | pink = 0xffc0cb, 120 | plum = 0xdda0dd, 121 | powderblue = 0xb0e0e6, 122 | purple = 0x800080, 123 | red = 0xff0000, 124 | rosybrown = 0xbc8f8f, 125 | royalblue = 0x4169e1, 126 | saddlebrown = 0x8b4513, 127 | salmon = 0xfa8072, 128 | sandybrown = 0xf4a460, 129 | seagreen = 0x2e8b57, 130 | seashell = 0xfff5ee, 131 | sienna = 0xa0522d, 132 | silver = 0xc0c0c0, 133 | skyblue = 0x87ceeb, 134 | slateblue = 0x6a5acd, 135 | slategray = 0x708090, 136 | slategrey = 0x708090, 137 | snow = 0xfffafa, 138 | springgreen = 0x00ff7f, 139 | steelblue = 0x4682b4, 140 | tan = 0xd2b48c, 141 | teal = 0x008080, 142 | thistle = 0xd8bfd8, 143 | tomato = 0xff6347, 144 | turquoise = 0x40e0d0, 145 | violet = 0xee82ee, 146 | violetred = 0xd02090, 147 | wheat = 0xf5deb3, 148 | white = 0xffffff, 149 | whitesmoke = 0xf5f5f5, 150 | yellow = 0xffff00, 151 | yellowgreen = 0x9acd32, 152 | } 153 | 154 | function color2rgb(col) 155 | local r, g, b, a 156 | if col:find("#", 1, true) then 157 | r = tonumber("0x"..col:sub(2, 3)) 158 | g = tonumber("0x"..col:sub(4, 5)) 159 | b = tonumber("0x"..col:sub(6, 7)) 160 | else 161 | local val = colors[col] or 0 162 | r = math.floor(val / 0x10000) % 256 163 | g = math.floor(val / 0x100) % 256 164 | b = val % 256 165 | end 166 | return r, g, b, a 167 | end 168 | -------------------------------------------------------------------------------- /decor/decor.lua: -------------------------------------------------------------------------------- 1 | require "sprite" 2 | require "theme" 3 | require "click" 4 | loadmod "color2rgb" 5 | 6 | local function utf_ff(b, pos) 7 | if type(b) ~= 'string' or b:len() == 0 then 8 | return 0 9 | end 10 | local utf8 = (std.game.codepage == 'UTF-8' or std.game.codepage == 'utf-8') 11 | if not utf8 then return 1 end 12 | local i = pos or 1 13 | local l = 0 14 | if b:byte(i) < 0x80 then 15 | return 1 16 | end 17 | i = i + 1 18 | l = l + 1 19 | while b:byte(i) >= 0x80 and b:byte(i) <= 0xbf do 20 | i = i + 1 21 | l = l + 1 22 | if i > b:len() then 23 | break 24 | end 25 | end 26 | return l 27 | end 28 | 29 | local cache = { 30 | } 31 | 32 | function cache:new(max, ttl) 33 | local c = { 34 | cache = {}; 35 | list = {}; 36 | ttl = ttl or 4; 37 | max = max or 16; 38 | } 39 | self.__index = self 40 | return std.setmt(c, self) 41 | end 42 | 43 | function cache:add(name, value) 44 | local v = self.cache[name] 45 | if v then 46 | v.value = value 47 | v.use = v.use + 1 48 | return v.value 49 | end 50 | v = { name = name, value = value, use = 1 } 51 | self.cache[name] = v 52 | table.insert(self.list, 1, v) 53 | return v.value 54 | end 55 | 56 | function cache:get(name) 57 | local v = self.cache[name] 58 | if not v then 59 | return 60 | end 61 | v.use = v.use + 1 62 | return v.value 63 | end 64 | 65 | function cache:clear() 66 | local nr = #self.list 67 | local list = {} 68 | if nr <= self.max then 69 | return 70 | end 71 | local todel = nr - self.max 72 | for k, v in ipairs(self.list) do 73 | if v.use == 0 and todel > 0 then 74 | v.ttl = v.ttl - 1 75 | if v.ttl <= 0 then 76 | self.cache[v.name] = nil 77 | if DEBUG then 78 | dprint("cache purge: "..v.name) 79 | end 80 | todel = todel - 1 81 | else 82 | table.insert(list, v) 83 | end 84 | else 85 | table.insert(list, v) 86 | end 87 | end 88 | self.list = list 89 | end 90 | 91 | function cache:put(name) 92 | local v = self.cache[name] 93 | if not v then 94 | return 95 | end 96 | v.use = v.use - 1 97 | if v.use <= 0 then v.use = 0; v.ttl = self.ttl; end 98 | -- for k, vv in ipairs(self.list) do 99 | -- if vv == v then 100 | -- table.remove(self.list, k) 101 | -- table.insert(self.list, #self.list, v) 102 | -- break 103 | -- end 104 | -- end 105 | return v.value 106 | end 107 | 108 | local img = { 109 | cache = cache:new(); 110 | } 111 | 112 | function img:delete(v) 113 | 114 | end 115 | 116 | function img:clear() 117 | self.cache:clear() 118 | end 119 | 120 | function img:render(v) 121 | if v.frames and v.w and v.h then 122 | local delay = v.delay or 25 123 | local w, h = v.sprite:size() 124 | local width = math.floor(w / v.w) 125 | local height = math.floor(h / v.h) 126 | local frame = v.frame_nr or 0 127 | local yy = math.floor(frame / width) 128 | local xx = math.floor(frame % width) 129 | v.fx = xx * v.w 130 | v.fy = yy * v.h 131 | if instead.ticks() - (v.__delay or 0) >= delay then 132 | if frame < v.frames - 1 then 133 | frame = frame + 1 134 | else 135 | frame = 0 136 | end 137 | v.frame_nr = frame 138 | v.__delay = instead.ticks() 139 | end 140 | end 141 | if v.background then 142 | if v.fx and v.fy and v.w and v.h then 143 | v.sprite:copy(v.fx, v.fy, v.w, v.h, sprite.scr(), v.x - v.xc, v.y - v.yc) 144 | else 145 | v.sprite:copy(sprite.scr(), v.x - v.xc, v.y - v.yc) 146 | end 147 | return 148 | end 149 | if type(v.render) == 'function' then 150 | v.render(v) 151 | return 152 | end 153 | if v.fx and v.fy and v.w and v.h then 154 | v.sprite:draw(v.fx, v.fy, v.w, v.h, sprite.scr(), v.x - v.xc, v.y - v.yc, v.alpha) 155 | else 156 | v.sprite:draw(sprite.scr(), v.x - v.xc, v.y - v.yc, v.alpha) 157 | end 158 | end 159 | 160 | function img:new_spr(v, s) 161 | v.xc = v.xc or 0 162 | v.yc = v.yc or 0 163 | v.x = v.x or 0 164 | v.y = v.y or 0 165 | v.sprite = s 166 | if not s then 167 | return v 168 | end 169 | local w, h = s:size() 170 | if v.w then w = v.w end 171 | if v.h then h = v.h end 172 | if v.xc == true then 173 | v.xc = math.floor(w / 2) 174 | end 175 | if v.yc == true then 176 | v.yc = math.floor(h / 2) 177 | end 178 | v.w, v.h = w, h 179 | return v 180 | end 181 | 182 | function img:new(v) 183 | local fname = v[3] 184 | if type(fname) == 'function' then 185 | if not std.functions[fname] then 186 | std.err("Non declared function", 2) 187 | end 188 | local s = fname(v) 189 | if not s then 190 | std.err("Can not construct sprite", 2) 191 | end 192 | return self:new_spr(v, s) 193 | elseif type(fname) ~= 'string' then 194 | std.err("Wrong filename in image") 195 | end 196 | local s = self.cache:get(fname) 197 | if not s then 198 | local sp = sprite.new(fname) 199 | if not sp then 200 | std.err("Can not load sprite: "..fname, 2) 201 | end 202 | s = self.cache:add(fname, sp) 203 | end 204 | self.cache:put(fname) 205 | return self:new_spr(v, s) 206 | end 207 | 208 | local raw = { 209 | } 210 | 211 | function raw:delete(v) 212 | end 213 | function raw:clear() 214 | end 215 | function raw:render(v) 216 | if type(v.render) ~= 'function' then 217 | return 218 | end 219 | v.render(v) 220 | return 221 | end 222 | function raw:new(v) 223 | if type(v[3]) == 'function' then 224 | v[3](v) 225 | return v 226 | end 227 | return v 228 | end 229 | 230 | local fnt = { 231 | cache = cache:new(); 232 | } 233 | 234 | function fnt:key(name, size) 235 | return name .. std.tostr(size) 236 | end 237 | 238 | function fnt:clear() 239 | self.cache:clear() 240 | for k, v in ipairs(self.cache.list) do 241 | v.value.cache:clear() 242 | end 243 | end 244 | 245 | function fnt:_get(name, size) 246 | local f = self.cache:get(self:key(name, size)) 247 | if not f then 248 | local fnt = sprite.fnt(name, size) 249 | if not fnt then 250 | std.err("Can not load font", 2) 251 | end 252 | f = { fnt = fnt, cache = cache:new(256, 16) } 253 | self.cache:add(self:key(name, size), f) 254 | end 255 | return f 256 | end 257 | 258 | function fnt:get(name, size) 259 | return self:_get(name, size).fnt 260 | end 261 | 262 | function fnt:text_key(text, color, style) 263 | local key = std.tostr(color)..'#'..std.tostr(style or "")..'#'..tostring(text) 264 | return key 265 | end 266 | 267 | function fnt:text(name, size, text, color, style) 268 | local fn = self:_get(name, size); 269 | local key = self:text_key(text, color, style) 270 | local sp = fn.cache:get(key) 271 | if not sp then 272 | sp = fn.fnt:text(text, color, style) 273 | fn.cache:add(key, sp) 274 | end 275 | fn.cache:put(key) 276 | self:put(name, size) 277 | return sp 278 | end 279 | 280 | function fnt:put(name, size) 281 | self.cache:put(self:key(name, size)) 282 | end 283 | 284 | local txt_mt = { 285 | } 286 | 287 | local txt = { 288 | } 289 | 290 | function txt_mt:pages() 291 | return #self.__pages 292 | end 293 | 294 | function txt_mt:page(nr) 295 | if nr == nil then 296 | return self.page_nr 297 | end 298 | if nr > self:pages() then 299 | return false 300 | end 301 | if nr < 1 then 302 | return false 303 | end 304 | if self.typewriter then 305 | self.finished = false 306 | end 307 | txt:make_page(self, nr) 308 | return true 309 | end 310 | 311 | function txt_mt:next_page() 312 | if self.typewriter and self.started then 313 | self.typewriter = false 314 | txt:make_page(self, self.page_nr or 1) 315 | self.typewriter = true 316 | self.started = false 317 | self.finished = true 318 | return 319 | end 320 | return self:page((self.page_nr or 1) + 1) 321 | end 322 | 323 | local function make_align(l, width, t) 324 | if t == 'left' then 325 | return 326 | end 327 | if t == 'center' then 328 | local delta = math.floor((width - l.w) / 2) 329 | for _, v in ipairs(l) do 330 | v.x = v.x + delta 331 | end 332 | return 333 | end 334 | if t == 'right' then 335 | local delta = math.floor(width - l.w) 336 | for _, v in ipairs(l) do 337 | v.x = v.x + delta 338 | end 339 | return 340 | end 341 | if t == 'justify' then 342 | local n = 0 343 | for _, v in ipairs(l) do 344 | if not v.unbreak then 345 | n = n + 1 346 | end 347 | end 348 | n = n - 1 349 | if n == 0 then 350 | return 351 | end 352 | local delta = math.floor((width - l.w) / n) 353 | local ldelta = (width - l.w) % n 354 | local xx = 0 355 | for k, v in ipairs(l) do 356 | if k > 1 then 357 | if not v.unbreak then 358 | if k == 2 then 359 | xx = xx + ldelta 360 | end 361 | xx = xx + delta 362 | end 363 | v.x = v.x + xx 364 | end 365 | end 366 | return 367 | end 368 | end 369 | 370 | local function parse_xref(str) 371 | str = str:gsub("^{", ""):gsub("}$", "") 372 | local h = str:find("|", 1, true) 373 | if not h then 374 | return false, str 375 | end 376 | local l = str:sub(h + 1) 377 | h = str:sub(1, h - 1) 378 | return h, l 379 | end 380 | 381 | local function preparse_links(text, links) 382 | local links = {} 383 | local link_id = 0 384 | 385 | local s = std.for_each_xref(text, 386 | function(str) 387 | local h, l = parse_xref(str) 388 | if not h then 389 | std.err("Wrong xref: "..str, 2) 390 | end 391 | local o = "" 392 | link_id = link_id + 1 393 | for w in l:gmatch("[^ \t]+") do 394 | if o ~= '' then o = o ..' ' end 395 | table.insert(links, {h, w, link_id}) 396 | o = o .. "\3".. std.tostr(#links) .."\4" 397 | end 398 | return o 399 | end) 400 | s = s:gsub('\\?'..'[{}]', { ['\\{'] = '{', ['\\}'] = '}' }) 401 | return s, links 402 | end 403 | 404 | function txt:make_page(v, nr) 405 | local page = nr or v.page_nr or 1 406 | local lines = v.__lines 407 | local spr = v.sprite 408 | local size = v.size or std.tonum(theme.get 'win.fnt.size') 409 | local color = v.color or theme.get('win.col.fg') 410 | local link_color = v.color_link or theme.get('win.col.link') 411 | local alink_color = v.color_alink or theme.get('win.col.alink') 412 | local font = v.font or theme.get('win.fnt.name') 413 | v.page_nr = page 414 | if v.w == 0 or v.h == 0 then 415 | return 416 | end 417 | if not v.__spr_blank then 418 | v.__spr_blank = sprite.new(v.w, v.h) 419 | end 420 | local lnr = v.__pages[page] 421 | v.__spr_blank:copy(v.sprite) 422 | if #lines == 0 then return end 423 | local off = lines[lnr].y 424 | v.__offset = off 425 | for _ = lnr, #lines do 426 | local l = lines[_] 427 | if l.y + l.h - off > v.h or l.pgbrk then 428 | break 429 | end 430 | for _, w in ipairs(l) do 431 | if not w.spr and w.w > 0 then 432 | w.spr = fnt:text(font, size, w.txt, 433 | w.id and link_color or color, w.style) 434 | end 435 | if w.id then -- link 436 | if not w.link then 437 | w.link = fnt:text(font, size, w.txt, alink_color, w.style) 438 | end 439 | else 440 | w.link = nil 441 | end 442 | if w.spr then 443 | w.spr:copy(v.sprite, w.x, w.y - off) 444 | end 445 | end 446 | end 447 | if v.typewriter then 448 | v.step = 0; -- typewriter effect 449 | if not v.__spr_blank then 450 | v.__spr_blank = sprite.new(v.w, v.h) 451 | end 452 | if not v.finished then 453 | v.started = true 454 | v.finished = false 455 | v.__spr_blank:copy(v.sprite) 456 | end 457 | end 458 | end 459 | 460 | function txt:new(v) 461 | local text = v[3] 462 | if type(text) == 'function' then 463 | if not std.functions[text] then 464 | std.err("Non declared function", 2) 465 | end 466 | text = text(v) 467 | end 468 | if type(text) ~= 'string' then 469 | std.err("Wrong text in txt decorator") 470 | end 471 | 472 | local align = v.align or 'left' 473 | local style = v.style or 0 474 | local font = v.font or theme.get('win.fnt.name') 475 | local intvl = v.interval or std.tonum(theme.get 'win.fnt.height') 476 | local size = v.size or std.tonum(theme.get 'win.fnt.size') 477 | 478 | local x, y = 0, 0 479 | v.fnt = fnt:get(font, size) 480 | local spw, _ = v.fnt:size(" ") 481 | 482 | local next = pixels.new(_, _) 483 | 484 | local r, g, b = color2rgb(v.color or theme.get('win.col.fg')) 485 | 486 | next:fill_poly({0, 0, _ - 1, 0, _ / 2, _ / 2 - 1}, r, g, b) 487 | next:polyAA({0, 0, _ - 1, 0, _ / 2, _ / 2 - 1}, r, g, b) 488 | 489 | v.btn = next:sprite() 490 | 491 | local lines = {} 492 | local line = { h = v.fnt:height() } 493 | local link_list = {} 494 | local maxw = v.w 495 | local maxh = v.h 496 | local W = 0 497 | local H = 0 498 | 499 | local function newline() 500 | line.y = y 501 | line.w = 0 502 | if #line > 0 then 503 | line.w = line[#line].x + line[#line].w 504 | end 505 | local h = v.fnt:height() 506 | for _, w in ipairs(line) do 507 | if w.h > h then 508 | h = w.h 509 | end 510 | end 511 | y = y + h * intvl 512 | if y > H then 513 | H = y 514 | end 515 | if #line > 0 then 516 | if #line == 1 and line[1].txt == '[break]' then line.pgbrk = true end 517 | table.insert(lines, line) 518 | end 519 | line = { h = v.fnt:height() } 520 | x = 0 521 | if maxh and y > maxh then 522 | return true 523 | end 524 | end 525 | 526 | local links 527 | text, links = preparse_links(text) 528 | local styles = {} 529 | local ww 530 | for w in text:gmatch("[^ \t]+") do 531 | while w and w ~= '' do 532 | local s, _ = w:find("\n", 1, true) 533 | if not s then 534 | ww = w 535 | w = false 536 | elseif s > 1 then 537 | ww = w:sub(1, s - 1) 538 | w = w:sub(s) 539 | else -- s == 1 540 | ww = '\n' 541 | w = w:sub(2) 542 | end 543 | if ww == '\n' then 544 | newline() 545 | else 546 | local t, act 547 | local applist = {} 548 | local xx = 0 549 | 550 | while ww and ww ~= '' do 551 | s, _ = ww:find("\3[0-9]+\4", 1) 552 | local id 553 | if s == 1 then 554 | local n = std.tonum(ww:sub(s + 1, _ - 1)) 555 | local ll = links[n] 556 | act, t, id = ll[1], ll[2], ll[3] 557 | ww = ww:sub(_ + 1) 558 | elseif s then 559 | t = ww:sub(1, s - 1) 560 | ww = ww:sub(s) 561 | else 562 | t = ww 563 | ww = false 564 | end 565 | while t:find("^%[[ibu]%]") do 566 | table.insert(styles, t:sub(2, 2)) 567 | t = t:gsub("^%[[ibu]%]", "") 568 | end 569 | 570 | local st = style 571 | 572 | local m = { b = 1, i = 2, u = 4 } 573 | 574 | for i = 1, #styles do 575 | st = st + m[styles[i]] 576 | end 577 | 578 | while t:find("%[/[ibu]%]$") do 579 | table.remove(styles, #styles) 580 | t = t:gsub("%[/[ibu]%]$", "") 581 | end 582 | local width, height = v.fnt:size(t, st) 583 | if height > line.h then 584 | line.h = height 585 | end 586 | 587 | if t == '[pause]' then 588 | width = 0 589 | end 590 | 591 | local witem = { style = st, 592 | action = act, id = id, x = xx, y = y, 593 | w = width, h = height, txt = t } 594 | if id then 595 | table.insert(link_list, witem) 596 | end 597 | table.insert(applist, witem) 598 | xx = xx + width 599 | end 600 | local sx = 0; 601 | 602 | if maxw and x + xx + spw > maxw and #line > 0 then 603 | newline() 604 | else 605 | sx = x 606 | end 607 | 608 | for k, v in ipairs(applist) do 609 | v.y = y 610 | v.x = v.x + sx 611 | x = v.x + v.w 612 | if k ~= 1 then 613 | v.unbreak = true 614 | end 615 | v.line = line 616 | table.insert(line, v) 617 | end 618 | if #applist > 0 then 619 | for i = #applist, 1, -1 do 620 | if applist[i].w > 0 then 621 | x = x + spw 622 | break 623 | end 624 | end 625 | end 626 | if x > W then 627 | W = x 628 | end 629 | end 630 | end 631 | end 632 | 633 | if #line > 0 then 634 | newline() 635 | end 636 | 637 | if (maxw or W) ~= 0 and (maxh or H) ~= 0 then 638 | v.sprite = sprite.new(maxw or W, maxh or H) 639 | end 640 | local pages = {} 641 | local off = 0; 642 | if #lines >= 1 then 643 | table.insert(pages, 1) 644 | end 645 | local brk 646 | for _, l in ipairs(lines) do 647 | l.page = #pages 648 | if l.pgbrk then 649 | brk = true 650 | elseif (l.y + l.h - off > (maxh or H)) or brk then 651 | off = l.y 652 | table.insert(pages, _) 653 | l.page = l.page + 1 654 | brk = false 655 | else 656 | make_align(l, maxw or W, align) 657 | brk = false 658 | end 659 | end 660 | v.__pages = pages 661 | v.__lines = lines 662 | v.__link_list = link_list 663 | if v.sprite then 664 | v.w, v.h = v.sprite:size() 665 | else 666 | v.w, v.h = 0, 0 667 | end 668 | if #link_list > 0 or #pages > 1 then 669 | v.click = true 670 | end 671 | std.setmt(v, txt_mt) 672 | txt_mt.__index = txt_mt 673 | self:make_page(v) 674 | return img:new_spr(v, v.sprite) 675 | end 676 | 677 | function txt:make_tw(v, step) 678 | local n = 0 679 | local spr = v.sprite 680 | local lnr = v.__pages[v.page_nr] 681 | for _ = lnr, #v.__lines do 682 | if n >= step then 683 | break 684 | end 685 | local l = v.__lines[_] 686 | if l.y + l.h - v.__offset > v.h or l.pgbrk then 687 | v.started = false 688 | v.finished = true 689 | break 690 | end 691 | for _, w in ipairs(l) do 692 | if n >= step then 693 | break 694 | end 695 | if w.txt:len() + n <= step then 696 | n = n + w.txt:len() 697 | n = n + 1 698 | if n >= step and w.spr then 699 | w.spr:copy(spr, w.x, w.y - v.__offset) 700 | end 701 | else 702 | local nm = step - n 703 | local i = 1 704 | while i < nm do 705 | i = i + utf_ff(w.txt, i) 706 | end 707 | step = step + i - nm 708 | local txt = w.txt:sub(1, i - 1) 709 | local ww, hh = v.fnt:size(txt) 710 | if w.spr then 711 | if type(decor.beep) == 'function' then 712 | decor.beep(v) 713 | end 714 | w.spr:copy(0, 0, ww, hh, spr, w.x, w.y - v.__offset) 715 | end 716 | n = step 717 | end 718 | end 719 | end 720 | v.step = n 721 | if n < step then 722 | v.started = false 723 | v.finished = true 724 | end 725 | return step > n 726 | end 727 | 728 | function txt:link(v, x, y) 729 | if v.typewriter and v.started then 730 | return 731 | end 732 | local off = v.__offset or 0 733 | y = y + off 734 | 735 | for _, w in ipairs(v.__link_list) do 736 | if w.line.page == (v.page_nr or 1) then 737 | if x >= w.x and y >= w.y then 738 | if x < w.x + w.w and y < w.y + w.h then 739 | return w, _ 740 | end 741 | local next = v.__link_list[_ + 1] 742 | if next and next.id == w.id and 743 | x < next.x and y < next.y + next.h then 744 | return w, _ 745 | end 746 | end 747 | end 748 | end 749 | end 750 | 751 | function txt:click(v, press, x, y) 752 | local w = self:link(v, x, y) 753 | if w then 754 | return std.cmd_parse(w.action) 755 | end 756 | return {} 757 | end 758 | 759 | function txt:render(v) 760 | if v.w == 0 or v.h == 0 then 761 | return 762 | end 763 | if v.typewriter and v.started then 764 | local d = instead.ticks() - (v.__last_tw or 0) 765 | decor.dirty = true 766 | if d > (v.delay or 25) then 767 | v.__last_tw = instead.ticks() 768 | v.step = (v.step or 0) + (v.speed or 1) 769 | txt:make_tw(v, v.step) 770 | end 771 | img:render(v) 772 | return 773 | end 774 | local x, y = instead.mouse_pos() 775 | x = x - v.x + v.xc 776 | y = y - v.y + v.yc 777 | local w = txt:link(v, x, y) 778 | 779 | local action = w and w.action or false 780 | local id = w and w.id or false 781 | 782 | for _, w in ipairs(v.__link_list) do 783 | if w.line.page == (v.page_nr or 1) then 784 | if w.id == id then 785 | if not w.__active then 786 | w.__active = true 787 | w.link:copy(v.sprite, w.x, w.y - v.__offset) 788 | end 789 | else 790 | if w.__active then 791 | w.__active = false 792 | w.spr:copy(v.sprite, w.x, w.y - v.__offset) 793 | end 794 | end 795 | decor.dirty = true 796 | end 797 | end 798 | img:render(v) 799 | if v:page() < v:pages() and v.btn then 800 | local w, h = v.btn:size() 801 | v.btn:draw(sprite.scr(), v.x + v.w - v.xc - w, v.y + v.h - v.yc - h) 802 | end 803 | end 804 | 805 | function txt:delete(v) 806 | if v.sprite then 807 | fnt:put(v.font or theme.get('win.fnt.name'), v.size) 808 | end 809 | end 810 | 811 | decor = obj { 812 | nam = '@decor'; 813 | { 814 | img = img; 815 | fnt = fnt; 816 | txt = txt; 817 | raw = raw; 818 | dirty = false; 819 | objects = { 820 | } 821 | }; 822 | bgcol = 'black'; 823 | } 824 | --[[ 825 | decor:img{ 'hello', 'img' } 826 | ]]-- 827 | 828 | function decor:zap() 829 | local l = {} 830 | for k, v in pairs(self.objects) do 831 | table.insert(l, k) 832 | end 833 | for _, name in ipairs(l) do 834 | local tt = self.objects[name].type 835 | self[tt]:delete(self.objects[name]) 836 | self.objects[name] = nil 837 | end 838 | end 839 | 840 | function decor:new(v) 841 | if type(v) == 'string' then 842 | v = self.objects[v] 843 | end 844 | local name = v[1] 845 | local t = v[2] 846 | if not v.z then 847 | v.z = 0 848 | end 849 | if type(name) ~= 'string' then 850 | std.err("Wrong parameter to decor:new(): name", 2) 851 | end 852 | if self.objects[name] then 853 | local tt = self.objects[name].type 854 | self[tt]:delete(self.objects[name]) 855 | end 856 | if t == nil then 857 | self.objects[name] = nil 858 | return 859 | end 860 | if type(t) ~= 'string' then 861 | std.err("Wrong parameter to decor:new(): type", 2) 862 | end 863 | if not self[t] or type(self[t].new) ~= 'function' then 864 | std.err("Wrong type decorator: "..t, 2) 865 | end 866 | v.name = name 867 | v.type = t 868 | self.objects[name] = self[t]:new(v) 869 | return v 870 | end; 871 | 872 | function decor:get(n) 873 | if type(n) ~= 'string' then 874 | std.err("Wrong parameter to decor:get(): name", 2) 875 | end 876 | return self.objects[n] 877 | end 878 | 879 | local after_list = {} 880 | 881 | function decor:process() 882 | local t = instead.ticks() 883 | for _, v in pairs(self.objects) do 884 | if not v.hidden and type(v.process) == 'function' then 885 | decor.dirty = true 886 | if t - (v.__last_time or 0) > (v.speed or 25) then 887 | v:process() 888 | v.__last_time = t 889 | end 890 | end 891 | if v.frames then decor.dirty = true end 892 | end 893 | end 894 | 895 | function decor:render() 896 | local list = {} 897 | if not decor.dirty then 898 | return 899 | end 900 | decor.dirty = false 901 | after_list = {} 902 | for _, v in pairs(self.objects) do 903 | local z = v.z or 0 904 | if not v.hidden then 905 | if z >= 0 then 906 | table.insert(list, v) 907 | else 908 | table.insert(after_list, v) 909 | end 910 | end 911 | end 912 | table.sort(list, function(a, b) 913 | if a.z == b.z then return a.name < b.name end 914 | return a.z > b.z 915 | end) 916 | table.sort(after_list, function(a, b) 917 | if a.z == b.z then return a.name < b.name end 918 | return a.z > b.z 919 | end) 920 | sprite.scr():fill(self.bgcol) 921 | for _, v in ipairs(list) do 922 | self[v.type]:render(v) 923 | end 924 | end 925 | std.mod_init(function(s) 926 | local oldrender = sprite.render_callback() 927 | sprite.render_callback( 928 | function() 929 | for _, v in ipairs(after_list) do 930 | decor[v.type]:render(v) 931 | end 932 | if oldrender then 933 | oldrender() 934 | end 935 | end) 936 | end) 937 | 938 | function decor:click_filter(press, x, y) 939 | local c = {} 940 | for _, v in pairs(self.objects) do 941 | if v.click and x >= v.x - v.xc and y >= v.y - v.yc and 942 | x < v.x - v.xc + v.w and y < v.y - v.yc + v.h then 943 | if v[2] == 'txt' then 944 | if not press then 945 | table.insert(c, v) 946 | end 947 | else 948 | table.insert(c, v) 949 | end 950 | end 951 | end 952 | if #c == 0 then 953 | return false 954 | end 955 | local e = c[1] 956 | for _, v in ipairs(c) do 957 | if v.z == e.z then 958 | if v.name > e.name then 959 | e = v 960 | end 961 | elseif v.z < e.z then 962 | e = v 963 | end 964 | end 965 | return e 966 | end 967 | 968 | function decor:cache_clear() 969 | self.img:clear(); 970 | self.fnt:clear(); 971 | end 972 | 973 | function decor:load() 974 | -- for _, v in pairs(self.fonts) do 975 | -- self:fnt(v) 976 | -- end 977 | -- for _, v in pairs(self.sprites) do 978 | -- self:spr(v) 979 | -- end 980 | for _, v in pairs(self.objects) do 981 | self:new(v) 982 | end 983 | end 984 | std.mod_cmd( 985 | function(cmd) 986 | if cmd[1] ~= '@decor_click' then 987 | return 988 | end 989 | local nam = cmd[2] 990 | local e = decor.objects[nam] 991 | local t = e[2] 992 | local press, x, y, btn = cmd[3], cmd[4], cmd[5], cmd[6] 993 | local r, v 994 | local a 995 | if type(decor[t].click) == 'function' then 996 | a = decor[t]:click(e, press, x, y, btn) 997 | else 998 | a = { } 999 | end 1000 | table.insert(a, 1, nam) 1001 | table.insert(a, 2, press) 1002 | table.insert(a, 3, x - e.xc) 1003 | table.insert(a, 4, y - e.yc) 1004 | table.insert(a, 5, btn) 1005 | 1006 | local r, v = std.call(std.here(), 'ondecor', std.unpack(a)) 1007 | if not r and not v then 1008 | r, v = std.call(std.game, 'ondecor', std.unpack(a)) 1009 | end 1010 | if not r and not v then 1011 | return nil, false 1012 | end 1013 | return r, v 1014 | end) 1015 | std.mod_start( 1016 | function(load) 1017 | if load then 1018 | for k, _ in pairs(theme.vars) do 1019 | theme.restore(k) 1020 | end 1021 | end 1022 | theme.set('scr.gfx.scalable', 5) 1023 | instead.wait_use(false) 1024 | instead.grab_events(true) 1025 | if load then 1026 | sprite.scr():fill(decor.bgcol) 1027 | decor:load() 1028 | decor.dirty = true 1029 | end 1030 | -- decor:render() 1031 | end) 1032 | 1033 | std.mod_step( 1034 | function(state) 1035 | if not state then 1036 | if std.cmd[1] == '@timer' then 1037 | decor:cache_clear() 1038 | decor:process() 1039 | decor:render() 1040 | if timer:get() == 0 then 1041 | instead.timer(0) 1042 | end 1043 | end 1044 | return 1045 | end 1046 | if iface:raw_mode() then -- debugger? 1047 | return 1048 | end 1049 | if std.cmd[1] == 'load' or std.cmd[1] == 'look' then 1050 | if timer:get() == 0 then 1051 | instead.timer(1) 1052 | end 1053 | else 1054 | decor:cache_clear() 1055 | decor:process() 1056 | decor:render() 1057 | end 1058 | end) 1059 | 1060 | local input = std.ref '@input' 1061 | local clickfn = input.click 1062 | 1063 | function input:click(press, btn, x, y, px, py, ...) 1064 | local e = decor:click_filter(press, x, y) 1065 | if e then 1066 | x = x - e.x + e.xc 1067 | y = y - e.y + e.yc 1068 | local a 1069 | for _, v in std.ipairs {e[1], press, x, y, btn} do 1070 | a = (a and (a..', ') or ' ') .. std.dump(v) 1071 | end 1072 | return '@decor_click'.. (a or '') 1073 | end 1074 | if clickfn then 1075 | return clickfn(input, press, btn, x, y, px, py, ...) 1076 | end 1077 | return false 1078 | end 1079 | 1080 | function D(n) 1081 | decor.dirty = true 1082 | if n == nil then 1083 | return decor:zap() 1084 | end 1085 | if type(n) == 'table' then 1086 | return decor:new(n) 1087 | end 1088 | return decor:get(n) 1089 | end 1090 | 1091 | function T(nam, val) 1092 | if val then 1093 | return theme.set(nam, tostring(val)) 1094 | else 1095 | return theme.get(nam) 1096 | end 1097 | end 1098 | 1099 | std.mod_save(function(fp) 1100 | fp:write(string.format("std'@decor'.objects = %s\n", std.dump(decor.objects, false))) 1101 | end) 1102 | -------------------------------------------------------------------------------- /decor/main3.lua: -------------------------------------------------------------------------------- 1 | require "timer" 2 | loadmod "decor" 3 | --[[ 4 | Создание декоратора: 5 | D { "имя декоратора", "тип декоратора", параметры ... } 6 | Удаление декоратора: 7 | D { "имя декоратора" } 8 | 9 | Получение декоратора, для изменения его параметров: 10 | D"имя декоратора" 11 | 12 | Пересоздание декоратора: 13 | D(D"имя декоратора") 14 | 15 | Общие аргументы декораторов: 16 | x, y - позиции на сцене 17 | xc, yc - точка центра декоратора (по умолчанию 0, 0 -- левый верхний угол) 18 | xc и yc могут принимать значение true - тогда xc и/или yc расчитаются самостоятельно как центр картинки 19 | w, h - ширина и высотра. Если не заданы, в результате создания декоратора будут вычислены самостоятельно 20 | z - слой. Чем больше - тем дальше от нас. Отрицательные значения - ПЕРЕД слоем текста, положительные - ПОСЛЕ. 21 | click -- если равен true - то клики по этому объекту будут доставляться в here():ondecor() или game:ondecor() 22 | 23 | Например: 24 | function game:ondecor(name, press, x, y, btn) 25 | name - имя декоратора 26 | press - нажато или отжато 27 | x, y -- координаты относительно декоратора (с учетом xc, yc) 28 | btn - кнопка 29 | 30 | hidden -- если равен true - то декоратор не видим 31 | 32 | Если вы используете анимацию, или подсветку ссылок в текстовом декораторе нужно включить таймер на желаемую частоту, 33 | например: 34 | timer:set(50) 35 | 36 | Типы декораторов: 37 | 38 | "img" - картинка или анимация 39 | Параметры: 40 | сначала идет графический файл, из которого будет создан декоратор. 41 | Вместо файла можно задать declared функцию, возвращающую спрайт. 42 | frames = число -- если это анимация. В анимации кадры записаны в одном полотне. Размер каждого кадра задается w и h. 43 | delay = число мс -- задержка анимации. 44 | background - если true, этот спрайт считается фоном и просто копируется (быстро). Для фонов ставьте z побольше. 45 | 46 | fx, fy - числа - если рисуем картинку из полотна, можно указать позицию в котором она находится 47 | 48 | Пример: 49 | D {"cat", "img", "anim.png", x = -64, y = 48, frames = 3, w = 64, h = 54, delay = 100, click = true } 50 | D {"title", "img", "title.png", x = 400, y = 300, xc = true, yc = true } -- по центру, если тема 800x600 51 | 52 | 53 | "txt" - текстовое поле 54 | В текстовом поле создается текст с требуемым шрифтом. 55 | В тексте могут быть переводы строк '\n' и ссылки {ссылка|текст}. 56 | Параметры: 57 | font - файл шрифта. Если не указан, берется из темы 58 | size - размер шрифта. Если не указан, берется из темы 59 | interval - интервал. Если не указан, берется из темы 60 | style - число Если не указано, то 0 (обычный) 61 | color - цвет, если не указано, берется из темы 62 | color_link, color_alink - цвет ссылки/подсвеченной ссылки (если не указано, берется из темы) 63 | 64 | Ссылки обрабатываются как у декораторов. Например: 65 | 66 | function game:ondecor(name, press, x, y, btn, act, ...) 67 | press - нажатие или отжатие (для текстовых декораторов приходит только отжатие 68 | x, y -- координаты ОТНОСИТЕЛЬНО декоратора 69 | name -- имя декоратора 70 | act и ... -- ссылка и ее аргументы 71 | Например {walk 'main'|Ссылка} 72 | 73 | function game:ondecor(name, press, x, y, btn, act, where) 74 | act будет равен 'walk' 75 | where будет равно 'main' 76 | 77 | T('параметр темы', значение) -- смена параметров темы, которые попадут в save 78 | ]]-- 79 | 80 | obj { 81 | nam = 'milk'; 82 | dsc = [[На полу стоит блюдце с {молоком}.]]; 83 | act = function() 84 | p [[Это для котика.]]; 85 | end; 86 | } 87 | 88 | room { 89 | nam = 'main'; 90 | title = 'ДЕКОРАТОРЫ'; 91 | dsc = [[Привет, мир!]]; 92 | obj = { 'milk' }; 93 | ondecor = function(s, name, press) -- котика обработаем в комнате 94 | if name == 'cat' and press then 95 | local mew = { 'Мяу!', 'Муррр!', 'Мурлык!', 'Мяуууу! Мяуууу!', 'Дай поесть!' }; 96 | p (mew[rnd(#mew)]) 97 | return 98 | end 99 | return false -- а все остальное -- в game 100 | end 101 | } 102 | 103 | local text = [[Привет любителям и авторам INSTEAD! 104 | [break] 105 | Это простая демонстрация 106 | альфа версии декораторов. 107 | [break] 108 | Название позаимствовано от FireURQ. 109 | [break] 110 | Надеюсь, вам понравится INSTEAD 3.2! 111 | Теперь вы можете нажать на {restart|ссылку}.]]; 112 | function game:timer() 113 | return false 114 | end 115 | function game:ondecor(name, press, x, y, btn, act, a, b) 116 | -- обработчик кликов декораторов (кроме котика, который обработан в main) 117 | if name == 'text' and not act then 118 | D'text':next_page() 119 | return false 120 | end 121 | if act == 'restart' then 122 | D'text':page(1) 123 | p("click:", name, ":",a, " ", b, " ", x, ",", y) -- вывели информацию о клике 124 | return 125 | end 126 | return false 127 | end 128 | 129 | declare 'box_alpha' (function (v) 130 | return sprite.new("box:"..std.tostr(v.w).."x"..std.tostr(v.h)..",black"):alpha(32) 131 | end) 132 | 133 | declare 'flake' (function (d) 134 | d.y = d.y + rnd(3) 135 | d.x = d.x + rnd(3) - 2 136 | if d.y > 600 then 137 | d.y = 0 138 | d.x = rnd(800) 139 | end 140 | end) 141 | 142 | declare 'kitten' (function (cat) 143 | cat.x = cat.x + 2 144 | end) 145 | 146 | function init() 147 | timer:set(50) 148 | for i = 1, 100 do 149 | decor:new {"snow"..std.tostr(i), "img", "box:4x4,black", process = flake, x= rnd(800), y = rnd(600), xc = true, yc = true, z = -1 } 150 | end 151 | decor.bgcol = 'white' 152 | D {"cat", "img", "anim.png", process = kitten, x = -64, y = 48, frames = 3, w = 64, h = 54, delay = 100, click = true, z = -1} 153 | D {"bg", "img", box_alpha, xc = true, yc = true, x = 400, w = 180, y = 300, h = 148, z = 2 } 154 | D {"text", "txt", text, xc = true, yc = true, x = 400, w = 160, y = 300, align = 'left', hidden = false, h = 128, typewriter = true, z =1 } 155 | end 156 | -------------------------------------------------------------------------------- /extlib/extlib-ru.lua: -------------------------------------------------------------------------------- 1 | local lang = require "morph/lang-ru" 2 | local ex = require "extlib" 3 | 4 | std.mod_init( 5 | function() 6 | ex.mrd:init(lang) 7 | end) 8 | 9 | function ex.shortcut.vo(hint) 10 | return "в ".. hint 11 | end 12 | 13 | std.player.word = -"ты/мр,2л" 14 | 15 | --"помещать" 16 | ex.msg.INSERT = "{#Me} {#word/помещать,нст,#me} {#first/вн} в {#second/вн}." 17 | 18 | --"класть" 19 | ex.msg.PUTON = "{#Me} {#word/класть,нст,#me} {#first/вн} на {#second/вн}." 20 | 21 | --"закрыт" 22 | ex.msg.PUTCLOSED = "Но {#second} {#word/закрыт,#second}." 23 | 24 | --"открывать" 25 | ex.msg.OPEN = "{#Me} {#word/открывать,нст,#me} {#first/вн}." 26 | 27 | --"закрывать" 28 | ex.msg.CLOSE = "{#Me} {#word/закрывать,нст,#me} {#first/вн}." 29 | 30 | --"видеть" 31 | ex.msg.EXAM = "{#Me} не {#word/видеть,#me,нст} {#vo/{#first/пр}} ничего необычного."; 32 | 33 | --"брать" 34 | ex.msg.TAKE = "{#Me} {#word/брать,#me,нст} {#first/вн}." 35 | 36 | ex.msg.IS = "находится" 37 | ex.msg.ARE = "находятся" 38 | ex.msg.HERE = "здесь" 39 | ex.msg.IN = "В {#first/пр,2}" 40 | ex.msg.ON = "На {#first/пр,2}" 41 | ex.msg.AND = "и" 42 | --"открыт" 43 | ex.msg.IS_OPENED = "{#word/открыт,нст,#first}" 44 | --"закрыт" 45 | ex.msg.IS_CLOSED = "{#word/закрыт,нст,#first}" 46 | 47 | --"включать" 48 | ex.msg.SWITCHON = "{#Me} {#word/включать,#me,нст} {#first/вн}." 49 | --"выключать" 50 | ex.msg.SWITCHOFF = "{#Me} {#word/выключать,#me,нст} {#first/вн}." 51 | -------------------------------------------------------------------------------- /extlib/extlib.lua: -------------------------------------------------------------------------------- 1 | local mrd = require "morph/mrd" 2 | local type = type 3 | 4 | local ex = { shortcut = {}, msg = {}, mrd = mrd } 5 | local function pfmt(...) 6 | p(std.exfmt(...)) 7 | end 8 | 9 | function ex.save_ctx() 10 | return { 11 | first = std.first, 12 | second = std.second, 13 | first_hint = std.first_hint, 14 | second_hint = std.second_hint, 15 | } 16 | end 17 | 18 | function ex.restore_ctx(ctx) 19 | std.first, std.second = ctx.first, ctx.second 20 | std.first_hint, std.second_hint = ctx.first_hint, ctx.second_hint 21 | end 22 | 23 | local function pnoun(noun, ...) 24 | local ctx = ex.save_ctx() 25 | std.first = noun 26 | std.first_hint = noun:gram().hint 27 | local r = std.exfmt(...) 28 | ex.restore_ctx(ctx) 29 | return r 30 | end 31 | 32 | function std.obj:hint(hint) 33 | return self:gram()[ex.mrd.lang.gram_t[hint] or hint] 34 | end 35 | 36 | local function str_strip(str) 37 | return std.strip(str) 38 | end 39 | 40 | local function str_split(str, delim) 41 | local a = std.split(str, delim) 42 | for k, _ in ipairs(a) do 43 | a[k] = str_strip(a[k]) 44 | end 45 | return a 46 | end 47 | 48 | function std.obj:attr(str) 49 | local a = str_split(str, ", ") 50 | for _, v in ipairs(a) do 51 | local val = (v:find("~", 1, true) ~= 1) 52 | v = v:gsub("^~", "") 53 | self['__attr__' .. v] = val 54 | end 55 | return self 56 | end 57 | 58 | function std.obj:hasnt(attr) 59 | return not self:has(attr) 60 | end 61 | 62 | function std.obj:has(attr) 63 | attr = std.strip(attr) 64 | local val = (attr:find("~", 1, true) ~= 1) 65 | attr = attr:gsub("^~", "") 66 | if val then 67 | return self['__attr__' .. attr] 68 | else 69 | return not self['__attr__' .. attr] 70 | end 71 | end 72 | local table = std.table 73 | 74 | std.room.display = function(s) 75 | local deco = std.call(s, 'decor'); -- static decorations 76 | return std.par(std.scene_delim, deco or false, std.obj.display(s)) 77 | end 78 | 79 | std.obj.display = function(s) 80 | local r 81 | local after = {} 82 | if s:closed() then 83 | return 84 | end 85 | for i = 1, #s.obj do 86 | if r then 87 | r = r .. std.space_delim 88 | end 89 | local o = s.obj[i] 90 | if o:visible() then 91 | local dsc = std.call(o, 'dsc') 92 | if type(dsc) ~= 'string' then 93 | if o:hasnt'concealed' then 94 | table.insert(after, o) 95 | end 96 | else 97 | local disp = o:display() 98 | local d = o:__xref(std.par(' ', dsc, disp)) 99 | if type(d) == 'string' then 100 | r = (r or '').. d 101 | end 102 | end 103 | end 104 | end 105 | if #after == 0 then 106 | return r 107 | end 108 | if r then 109 | r = r .. std.scene_delim 110 | end 111 | if std.here() == s then 112 | r = (r or '').. ex.mrd.lang.cap(ex.msg.HERE) .. ' ' 113 | else 114 | if s:has 'supporter' then 115 | r = (r or '').. pnoun(s, ex.msg.ON) .. ' ' 116 | elseif s:has 'container' then 117 | r = (r or '').. pnoun(s, ex.msg.IN) .. ' ' 118 | end 119 | end 120 | if #after > 1 or after[1]:hint'plural' then 121 | r = r .. ex.msg.ARE 122 | if #after > 1 then 123 | r = r .. ': ' 124 | else 125 | r = r .. ' ' 126 | end 127 | else 128 | r = r .. ex.msg.IS ..' ' 129 | end 130 | for i = 1, #after do 131 | local o = after[i] 132 | local disp = '{'..std.nameof(o)..'|'..std.dispof(o)..'}' 133 | if o:has'openable' and not o:closed() then 134 | disp = disp .. " ("..pnoun(o, ex.msg.IS_OPENED)..")" 135 | end 136 | local d = o:__xref(disp); 137 | if i > 1 then 138 | if i == #after then 139 | r = r .. ' '..ex.msg.AND .. ' ' 140 | else 141 | r = r .. ', ' 142 | end 143 | end 144 | if type(d) == 'string' then 145 | r = (r or '').. d 146 | end 147 | end 148 | r = r .. '.' 149 | for i = 1, #after do 150 | local o = after[i] 151 | if not o:closed() or o:has'transparent' or o:has'supporter' then 152 | local d = o:display() 153 | if type(d) == 'string' then 154 | r = (r and (r .. std.space_delim) or '') .. d 155 | end 156 | end 157 | end 158 | return r 159 | end 160 | 161 | std.obj.tak = function(s) 162 | if s:has'item' then 163 | pfmt(ex.msg.TAKE) 164 | return 165 | end 166 | return false 167 | end 168 | 169 | std.obj.act = function(s) 170 | local u = std.call(s, 'onact') 171 | if u then 172 | return u 173 | end 174 | if s:has'openable' then 175 | if s:closed() then 176 | pfmt(ex.msg.OPEN) 177 | s:open() 178 | else 179 | pfmt(ex.msg.CLOSE) 180 | s:close() 181 | end 182 | return 183 | end 184 | if s:has'switchable' then 185 | if s:has'on' then 186 | pfmt(ex.msg.SWITCHOFF) 187 | s:attr'~on' 188 | else 189 | pfmt(ex.msg.SWITCHON) 190 | s:attr'on' 191 | end 192 | return 193 | end 194 | pfmt(ex.msg.EXAM) 195 | end 196 | 197 | std.obj.inv = function(s) 198 | local u = std.call(s, 'oninv') 199 | if u then 200 | return u 201 | end 202 | pfmt(ex.msg.EXAM) 203 | end 204 | 205 | std.obj.use = function(s, w) 206 | local u = std.call(s, 'onuse', w) 207 | if u then 208 | return u 209 | end 210 | if s:has'item' then 211 | if w:has'supporter' then 212 | pfmt(ex.msg.PUTON) 213 | place(s, w) 214 | return 215 | elseif w:has'container' then 216 | if w:closed() then 217 | pfmt(ex.msg.PUTCLOSED) 218 | return 219 | end 220 | pfmt(ex.msg.INSERT) 221 | place(s, w) 222 | return 223 | end 224 | end 225 | return false 226 | end 227 | 228 | std.callpush = function(v, w, ...) 229 | std.call_top = std.call_top + 1; 230 | if std.call_top == 1 then 231 | std.first = v 232 | std.second = w 233 | std.first_hint = v and v:gram().hint 234 | std.second_hint = std.is_obj(w) and w:gram().hint 235 | end 236 | std.call_ctx[std.call_top] = { txt = nil, self = v }; 237 | end 238 | 239 | function std.shortcut_obj(ob) 240 | if ob == '#first' then 241 | ob = std.first 242 | elseif ob == '#second' then 243 | ob = std.second 244 | elseif ob == '#firstwhere' then 245 | ob = std.first:where() 246 | elseif ob == '#secondwhere' then 247 | ob = std.second:where() 248 | elseif ob == '#me' then 249 | ob = std.me() 250 | elseif ob == '#where' then 251 | ob = std.me():where() 252 | elseif ob == '#here' then 253 | ob = std.here() 254 | else 255 | ob = false 256 | end 257 | return ob 258 | end 259 | 260 | local function shortcut(ob, hint) 261 | return ob:noun(hint) 262 | end 263 | 264 | function ex.shortcut.where(hint) 265 | return shortcut(std.me():where(), hint) 266 | end 267 | 268 | function ex.shortcut.firstwhere(hint) 269 | return shortcut(std.first:where(), hint) 270 | end 271 | 272 | function ex.shortcut.secondwhere(hint) 273 | return shortcut(std.second:where(), hint) 274 | end 275 | 276 | function ex.shortcut.here(hint) 277 | return shortcut(std.here(), hint) 278 | end 279 | 280 | function ex.shortcut.first(hint) 281 | return shortcut(std.first, hint) 282 | end 283 | 284 | function ex.shortcut.firstit(hint) 285 | return std.first:it(hint) 286 | end 287 | 288 | function ex.shortcut.second(hint) 289 | return shortcut(std.second, hint) 290 | end 291 | 292 | function ex.shortcut.me(hint) 293 | return shortcut(std.me(), hint) 294 | end 295 | 296 | local function hint_append(hint, h) 297 | if h == "" or not h then return hint end 298 | if hint == "" or not hint then return h end 299 | return hint .. ',' .. h 300 | end 301 | 302 | function ex.shortcut.word(hint) 303 | local w = str_split(hint, ",") 304 | if #w == 0 then 305 | return hint 306 | end 307 | local verb = w[1] 308 | table.remove(w, 1) 309 | hint = '' 310 | for _, k in ipairs(w) do 311 | if k == '#first' then 312 | hint = hint_append(hint, std.first_hint) 313 | elseif k == '#second' then 314 | hint = hint_append(hint, std.second_hint) 315 | elseif k:find("#", 1, true) == 1 then 316 | local ob = std.shortcut_obj(k) 317 | if not ob then 318 | std.err("Wrong shortcut word: "..k, 2) 319 | end 320 | hint = hint_append(hint, ob:gram().hint) 321 | else 322 | hint = hint_append(hint, k) 323 | end 324 | end 325 | local t = mrd:noun(verb .. '/' .. hint) 326 | return t 327 | end 328 | 329 | function ex.shortcut.if_hint(hint) 330 | local w = str_split(hint, ",") 331 | if #w < 3 then 332 | return hint 333 | end 334 | local attr = w[2] 335 | local ob = w[1] 336 | ob = std.shortcut_obj(ob) 337 | if not ob then 338 | std.err("Wrong object in if_has shortcut: "..hint, 2) 339 | end 340 | if not ob:hint(attr) then 341 | return w[4] or '' 342 | end 343 | return w[3] or '' 344 | end 345 | 346 | function ex.shortcut.if_has(hint) 347 | local w = str_split(hint, ",") 348 | if #w < 3 then 349 | return hint 350 | end 351 | local attr = w[2] 352 | local ob = w[1] 353 | ob = std.shortcut_obj(ob) 354 | if not ob then 355 | std.err("Wrong object in if_has shortcut: "..hint, 2) 356 | end 357 | if not ob:has(attr) then 358 | return w[4] or '' 359 | end 360 | return w[3] or '' 361 | end 362 | 363 | function std.exfmt(...) 364 | local args = {} 365 | for _, v in ipairs({...}) do 366 | local finish 367 | if type(v) == 'string' then 368 | repeat 369 | finish = true 370 | v = v:gsub("{#[^{}]*}", function(w) 371 | local ww = w 372 | w = w:gsub("^{#", ""):gsub("}$", "") 373 | local hint = w:gsub("^[^/]*/?", "") 374 | w = w:gsub("/[^/]*$", "") 375 | local cap = ex.mrd.lang.is_cap(w) 376 | w = w:lower() 377 | if ex.shortcut[w] then 378 | w = ex.shortcut[w](hint) 379 | if cap then 380 | w = ex.mrd.lang.cap(w) 381 | end 382 | else 383 | std.err("Wrong shortcut: ".. ww, 2) 384 | end 385 | finish = false 386 | return w 387 | end) 388 | until finish 389 | end 390 | table.insert(args, v) 391 | end 392 | local ret 393 | for i = 1, #args do 394 | ret = std.par('', ret or false, std.tostr(args[i])); 395 | end 396 | return ret 397 | end 398 | 399 | return ex 400 | -------------------------------------------------------------------------------- /extlib/main3.lua: -------------------------------------------------------------------------------- 1 | loadmod 'extlib-ru' 2 | 3 | obj { 4 | -"винтовка"; 5 | nam = "винтовка"; 6 | onuse = function(s, w) 7 | if w ^ 'ваза' then 8 | p [[Бах!]]; 9 | remove(w) 10 | return 11 | end 12 | return false 13 | end; 14 | }:attr 'item' 15 | 16 | obj { 17 | -"телевизор"; 18 | nam = "телевизор"; 19 | }:attr 'switchable'; 20 | 21 | obj { 22 | -"стол"; 23 | nam = "стол"; 24 | }:attr 'supporter': with { 'винтовка', 'ваза', 'коробка', 'телевизор' } 25 | 26 | obj { 27 | -"коробка"; 28 | nam = "коробка"; 29 | }:attr 'openable,container'; 30 | 31 | obj { 32 | -"ваза"; 33 | nam = "ваза"; 34 | }:attr 'container,item':with 'цветок' 35 | 36 | obj { 37 | -"цветок"; 38 | nam = "цветок"; 39 | }:attr 'item'; 40 | 41 | room { 42 | nam = 'main'; 43 | title = "extlib demo"; 44 | obj = { 'стол' }; 45 | } 46 | -------------------------------------------------------------------------------- /extlib/morph/lang-ru.lua: -------------------------------------------------------------------------------- 1 | error "Download https://github.com/instead-hub/metaparser/morph directory and put it into morph/" -------------------------------------------------------------------------------- /fading/fading.lua: -------------------------------------------------------------------------------- 1 | require "sprite" 2 | require "theme" 3 | require "timer" 4 | 5 | instead.fading = false 6 | 7 | local f = std.obj { 8 | { 9 | started = false; 10 | timer = false; 11 | effects = {}; 12 | }; 13 | delay = 20; -- default delay 14 | max = 16; -- default max 15 | effect = false; 16 | defeffect = false; 17 | nam = '@fading'; 18 | } 19 | 20 | function f.effects.fadeblack(s, src, dst) 21 | sprite.scr():fill('black') 22 | if s.step < s.max / 2 then -- fadeout old 23 | local alpha = 255 - (s.step * 2 / s.max) * 255; 24 | if alpha > 0 then 25 | src:draw(sprite.scr(), 0, 0, alpha); 26 | end 27 | else -- fadein new 28 | local alpha = ((s.step - 1 - s.max / 2) / s.max) * 255 * 2; 29 | if alpha > 0 then 30 | dst:draw(sprite.scr(), 0, 0, alpha); 31 | end 32 | end 33 | end 34 | 35 | function f.effects.fadewhite(s, src, dst) 36 | sprite.scr():fill('white') 37 | if s.step < s.max / 8 then -- fadeout old 38 | local pos = s.step * 8 / s.max 39 | local alpha = (1 - pos) * 255; 40 | if alpha > 0 then 41 | src:draw(sprite.scr(), 0, 0, alpha); 42 | end 43 | else -- fadein new 44 | local pos = (s.step - s.max / 8) / (s.max - s.max/ 8); 45 | local alpha = pos * 255 46 | if alpha > 0 then 47 | dst:draw(sprite.scr(), 0, 0, alpha); 48 | end 49 | end 50 | end 51 | 52 | function f.effects.crossfade(s, src, dst) 53 | local alpha = ((s.step - 1) / s.max) * 255; 54 | -- src:draw(sprite.scr(), 0, 0, 255 - alpha); 55 | src:copy(sprite.scr()); 56 | dst:draw(sprite.scr(), 0, 0, alpha); 57 | end 58 | 59 | function f.effects.move_left(s, src, dst) 60 | -- sprite.scr():fill('black') 61 | local x = theme.scr.w() * s.step / s.max 62 | src:copy(sprite.scr(), x, 0); 63 | dst:copy(sprite.scr(), x - theme.scr.w(), 0); 64 | end 65 | 66 | function f.effects.move_right(s, src, dst) 67 | -- sprite.scr():fill('black') 68 | local x = theme.scr.w() * s.step / s.max 69 | dst:copy(sprite.scr(), theme.scr.w() - x, 0); 70 | src:copy(sprite.scr(), -x, 0); 71 | end 72 | 73 | function f.effects.move_up(s, src, dst) 74 | -- sprite.scr():fill('black') 75 | local y = theme.scr.h() * s.step / s.max 76 | src:copy(sprite.scr(), 0, y); 77 | dst:copy(sprite.scr(), 0, y - theme.scr.h()); 78 | end 79 | 80 | function f.effects.move_down(s, src, dst) 81 | -- sprite.scr():fill('black') 82 | local y = theme.scr.h() * s.step / s.max 83 | dst:copy(sprite.scr(), 0, theme.scr.h() - y); 84 | src:copy(sprite.scr(), 0, -y); 85 | end 86 | 87 | local scr, scr2 88 | local cb = timer.callback 89 | 90 | function timer:callback(...) 91 | if f.started then 92 | return '@fading' 93 | end 94 | return cb(self, ...) 95 | end 96 | 97 | function f.start() 98 | if f.effect[1] == 'none' then 99 | f.started = false 100 | if f.defeffect then 101 | f.effect = std.clone(f.defeffect) 102 | end 103 | return 104 | end 105 | local old = sprite.direct() 106 | sprite.direct(true) 107 | sprite.scr():copy(scr) 108 | sprite.direct(old) 109 | f.timer = timer:get() 110 | f.effect.step = 0 111 | f.started = true 112 | instead.timer(f.effect.delay or 20) 113 | end 114 | 115 | function f.change(ops) 116 | if type(ops) == 'string' then 117 | ops = { ops } 118 | end 119 | ops.forever = true 120 | f.set(ops) 121 | end 122 | 123 | function f.set(ops) 124 | if type(ops) == 'string' then 125 | ops = { ops } 126 | end 127 | ops.delay = ops.delay or f.delay 128 | ops.max = ops.max or f.max 129 | f.effect = std.clone(ops) 130 | if ops.forever then 131 | f.defeffect = std.clone(f.effect) 132 | end 133 | end 134 | 135 | 136 | std.mod_cmd(function(cmd) 137 | if cmd[1] ~= '@fading' then 138 | if f.started and cmd[1] ~= 'save' 139 | and cmd[1] ~= 'way' 140 | and cmd[1] ~= 'inv' then 141 | return true, false 142 | end 143 | return 144 | end 145 | f.effect.step = f.effect.step + 1 146 | 147 | f.effects[f.effect[1]](f.effect, scr, scr2) 148 | 149 | if f.effect.step > f.effect.max then 150 | f.started = false 151 | if f.defeffect then 152 | f.effect = std.clone(f.defeffect) 153 | end 154 | instead.timer(f.timer) 155 | sprite.direct(false) 156 | return std.nop() 157 | end 158 | return 159 | end, -1) 160 | std.mod_init(function() 161 | f.change { 'crossfade', max = 8 }; 162 | local oldrender = sprite.render_callback() 163 | sprite.render_callback(function() 164 | if f.started and not sprite.direct() then 165 | sprite.direct(true) 166 | sprite.scr():copy(scr2) 167 | scr:copy(sprite.scr()) 168 | end 169 | if not f.started and oldrender then 170 | oldrender() 171 | end 172 | end) 173 | end) 174 | 175 | std.mod_start(function() 176 | scr = sprite.new(theme.get 'scr.w', theme.get 'scr.h') 177 | scr2 = sprite.new(theme.get 'scr.w', theme.get 'scr.h') 178 | if f.defeffect then 179 | f.effect = std.clone(f.defeffect) 180 | end 181 | end) 182 | 183 | std.mod_step(function(state) 184 | if not state then 185 | return 186 | end 187 | if (player_moved() or f.effect.now) and std.cmd[1] ~= 'load' and std.cmd[1] ~= '@fading' 188 | and std.cmd[1] ~= 'look' then 189 | f.start() 190 | end 191 | end) 192 | 193 | fading = f 194 | -------------------------------------------------------------------------------- /fading/main3.lua: -------------------------------------------------------------------------------- 1 | loadmod 'fading' 2 | --[[ 3 | fading.set {'имя эффекта', параметры } -- задание эффекта на 1 раз (на следующий переход) 4 | параметры: 5 | delay = 20 -- значение таймера 6 | max = 16 -- число итераций 7 | 8 | fading.change {'имя эффекта', параметры } -- задание эффекта навсегда (на все переходы) 9 | Это синоним эффекта по умолчанию. 10 | 11 | ]]-- 12 | local effects = { 13 | 'crossfade', 14 | 'fadeblack', 15 | 'fadewhite', 16 | 'move_left', 17 | 'move_right', 18 | 'move_up', 19 | 'move_down', 20 | } 21 | 22 | global 'effect' (1); 23 | obj { 24 | nam = 'эффект'; 25 | dsc = function() 26 | p("Эффект: {", effects[effect], "}") 27 | end; 28 | act = function(s) 29 | effect = effect + 1; 30 | if effect > #effects then effect = 1 end 31 | fading.change {effects[effect], max = 32, delay = 25} 32 | end; 33 | } 34 | 35 | room { 36 | nam = 'main'; 37 | obj = { 'эффект' }; 38 | way = { 'main2' }; 39 | } 40 | 41 | room { 42 | nam = 'main2'; 43 | obj = { 'эффект' }; 44 | way = { 'main' }; 45 | } 46 | -------------------------------------------------------------------------------- /fonts/fonts.lua: -------------------------------------------------------------------------------- 1 | -- example module 2 | require "sprite" 3 | require "theme" 4 | local std = stead 5 | local type = std.type 6 | local pairs = std.pairs 7 | local table = std.table 8 | 9 | local fnt = std.obj { 10 | nam = '$fnt'; 11 | { 12 | faces = {}; 13 | }; 14 | face = function(s, fn, name, size, scaled) 15 | if type(fn) ~= 'string' then 16 | std.err("Wrong argument to fnt:face()"..std.tostr(fn), 2) 17 | end 18 | if not size then 19 | size = std.tonum(theme.get 'win.fnt.size') 20 | end 21 | if scaled then 22 | size = sprite.font_scaled_size(size) 23 | end 24 | if not name then 25 | name = theme.get 'win.fnt.name' 26 | end 27 | s.faces[fn] = {} 28 | s.faces[fn].font = sprite.fnt(name, size) 29 | s.faces[fn].cache = {} 30 | s.faces[fn].list = {} 31 | end; 32 | cache_get = function(s, fn, w, color, t) 33 | local k = w..color..tostring(t) 34 | local c = s.faces[fn].cache 35 | if not c then 36 | return 37 | end 38 | if c[k].time ~= -1 then 39 | c[k].time = std.game:time() 40 | end 41 | return c[k] 42 | end; 43 | cache_clear = function(s, fn, age) 44 | local k, v 45 | local new_list = {} 46 | if not age then 47 | age = 0 48 | end 49 | 50 | local self = s.faces[fn] 51 | 52 | for k, v in ipairs(self.list) do 53 | local key = v.word..v.color..std.tostr(v.t) 54 | if v.time ~= -1 and std.game:time() - v.time >= age then 55 | self.cache[key] = nil 56 | else 57 | std.table.insert(new_list, v) 58 | if v.time ~= -1 then 59 | s:cache_add(fn, v.word, v.color, v.t, nil) -- renew time 60 | else 61 | s:cache_add(fn, v.word, v.color, v.t, -1) 62 | end 63 | end 64 | end 65 | self.list = new_list 66 | end; 67 | cache_add = function(s, fn, w, color, t, key, time) 68 | local k = w..color..tostring(t) 69 | local self = s.faces[fn] 70 | if not self.cache[k] then 71 | self.cache[k] = {} 72 | self.cache[k].img = self.font:text(w, color, t); 73 | self.cache[k].word = w; 74 | self.cache[k].color = color; 75 | self.cache[k].t = t; 76 | self.cache[k].time = std.game:time(); 77 | table.insert(self.list, self.cache[k]); 78 | end 79 | if not std.game and not time then 80 | time = -1 81 | end 82 | if time then 83 | self.cache[k].time = time 84 | else 85 | self.cache[k].time = std.game:time(); -- renew time 86 | end 87 | return self.cache[k] 88 | end; 89 | txt = function(self, fn, txt, color, t) 90 | local s, e; 91 | local ss = 1 92 | local res = '' 93 | if not color then 94 | color = theme.get 'win.col.fg' 95 | end 96 | if not t then 97 | t = 0 98 | end 99 | while true do 100 | local start = ss 101 | while true do 102 | local s1, e1 = txt:find("\\", ss) 103 | s, e = txt:find("[ \t\n^]+", ss); 104 | if not s1 or not s then 105 | break 106 | end 107 | if s1 < s then 108 | s, e = s1, e1 109 | ss = s + 2 110 | else 111 | break 112 | end 113 | end 114 | ss = start 115 | local w 116 | if s then s = s - 1 end 117 | w = txt:sub(ss, s); 118 | local c 119 | if w then 120 | if s then 121 | c = txt:sub(s + 1, e) 122 | end 123 | w = w:gsub("\\(.)", "%1") 124 | w = w:gsub("[ \t\n]+$", ""); 125 | end 126 | if w and w ~= '' and w ~= '\n' then 127 | self:cache_add(fn, w, color, t) 128 | res = res .. iface:img(self:cache_get(fn, w, color, t).img); 129 | end 130 | if not e then break end 131 | ss = e + 1 132 | if not c then c = '' end 133 | res = res .. c; 134 | end 135 | self:cache_add(fn, " ", color, t) 136 | local space = iface:img(self:cache_get(fn, " ", color, t).img) 137 | res = res:gsub(" ", space) 138 | return res; 139 | end; 140 | life = function(s) 141 | if std.me():moved() then 142 | for k, v in pairs(s.faces) do 143 | s:cache_clear(k, 2) 144 | end 145 | end 146 | end; 147 | act = function(s, face, ...) 148 | local a = {...} 149 | local txt = table.remove(a, #a) 150 | return s:txt(face, txt, std.unpack(a)); 151 | end; 152 | } 153 | std.mod_step(function(st) 154 | if st then 155 | fnt:life() 156 | end 157 | end) 158 | -------------------------------------------------------------------------------- /fonts/main3.lua: -------------------------------------------------------------------------------- 1 | require "fonts" 2 | local fnt = _'$fnt' 3 | fnt:face ('sans', 'sans.ttf', 33) 4 | room { 5 | nam = 'main'; 6 | dsc = '{$fnt sans}'; 7 | }:with { 8 | obj { 9 | dsc = 'Тут лежит {{$fnt sans|что\\|то}}'; 10 | act = '{$fnt sans|Вы нажали на что-то}'; 11 | }; 12 | obj { 13 | nam = 'test'; 14 | act = 'test!'; 15 | }; 16 | 17 | } -------------------------------------------------------------------------------- /fonts/sans.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instead-hub/stead3-modules/c97f6831fb3be47f9199cacf2677265cc0596bc9/fonts/sans.ttf -------------------------------------------------------------------------------- /keyboard/keyboard.lua: -------------------------------------------------------------------------------- 1 | require "fmt" 2 | require "noinv" 3 | require "keys" 4 | local std = stead 5 | 6 | local input = std.ref '@input' 7 | 8 | local keyb = { 9 | { "1", "2", "3", "4", "5", "6", "7", "8", "9", "0", "-", "=", }, 10 | { "q", "w", "e", "r", "t", "y", "u", "i", "o", "p", "[", "]", }, 11 | { "a", "s", "d", "f", "g", "h", "j", "k", "l", ";", "'", [[\]], }, 12 | { "z", "x", "c", "v", "b", "n", "m", ",", ".", "/", }, 13 | } 14 | 15 | local kbden = { 16 | shifted = { 17 | ["1"] = "!", 18 | ["2"] = "@", 19 | ["3"] = "#", 20 | ["4"] = "$", 21 | ["5"] = "%", 22 | ["6"] = "^", 23 | ["7"] = "&", 24 | ["8"] = "*", 25 | ["9"] = "(", 26 | ["0"] = ")", 27 | ["-"] = "_", 28 | ["="] = "+", 29 | ["["] = "{", 30 | ["]"] = "}", 31 | ["\\"] = "|", 32 | [";"] = ":", 33 | ["'"] = "\"", 34 | [","] = "<", 35 | ["."] = ">", 36 | ["/"] = "?", 37 | } 38 | } 39 | 40 | local kbdru = { 41 | ["q"] = "й", 42 | ["w"] = "ц", 43 | ["e"] = "у", 44 | ["r"] = "к", 45 | ["t"] = "е", 46 | ["y"] = "н", 47 | ["u"] = "г", 48 | ["i"] = "ш", 49 | ["o"] = "щ", 50 | ["p"] = "з", 51 | ["["] = "х", 52 | ["]"] = "ъ", 53 | ["a"] = "ф", 54 | ["s"] = "ы", 55 | ["d"] = "в", 56 | ["f"] = "а", 57 | ["g"] = "п", 58 | ["h"] = "р", 59 | ["j"] = "о", 60 | ["k"] = "л", 61 | ["l"] = "д", 62 | [";"] = "ж", 63 | ["'"] = "э", 64 | ["z"] = "я", 65 | ["x"] = "ч", 66 | ["c"] = "с", 67 | ["v"] = "м", 68 | ["b"] = "и", 69 | ["n"] = "т", 70 | ["m"] = "ь", 71 | [","] = "б", 72 | ["."] = "ю", 73 | ["`"] = "ё", 74 | ["/"] = ".", 75 | shifted = { 76 | ["q"] = "Й", 77 | ["w"] = "Ц", 78 | ["e"] = "У", 79 | ["r"] = "К", 80 | ["t"] = "Е", 81 | ["y"] = "Н", 82 | ["u"] = "Г", 83 | ["i"] = "Ш", 84 | ["o"] = "Щ", 85 | ["p"] = "З", 86 | ["["] = "Х", 87 | ["]"] = "Ъ", 88 | ["a"] = "Ф", 89 | ["s"] = "Ы", 90 | ["d"] = "В", 91 | ["f"] = "А", 92 | ["g"] = "П", 93 | ["h"] = "Р", 94 | ["j"] = "О", 95 | ["k"] = "Л", 96 | ["l"] = "Д", 97 | [";"] = "Ж", 98 | ["'"] = "Э", 99 | ["z"] = "Я", 100 | ["x"] = "Ч", 101 | ["c"] = "С", 102 | ["v"] = "М", 103 | ["b"] = "И", 104 | ["n"] = "Т", 105 | ["m"] = "Ь", 106 | [","] = "Б", 107 | ["."] = "Ю", 108 | ["`"] = "Ё", 109 | ["1"] = "!", 110 | ["2"] = "@", 111 | ["3"] = "#", 112 | ["4"] = ";", 113 | ["5"] = "%", 114 | ["6"] = ":", 115 | ["7"] = "?", 116 | ["8"] = "*", 117 | ["9"] = "(", 118 | ["0"] = ")", 119 | ["-"] = "_", 120 | ["="] = "+", 121 | ["/"] = ",", 122 | } 123 | } 124 | local kbdlower = { 125 | ['А'] = 'а', 126 | ['Б'] = 'б', 127 | ['В'] = 'в', 128 | ['Г'] = 'г', 129 | ['Д'] = 'д', 130 | ['Е'] = 'е', 131 | ['Ё'] = 'ё', 132 | ['Ж'] = 'ж', 133 | ['З'] = 'з', 134 | ['И'] = 'и', 135 | ['Й'] = 'й', 136 | ['К'] = 'к', 137 | ['Л'] = 'л', 138 | ['М'] = 'м', 139 | ['Н'] = 'н', 140 | ['О'] = 'о', 141 | ['П'] = 'п', 142 | ['Р'] = 'р', 143 | ['С'] = 'с', 144 | ['Т'] = 'т', 145 | ['У'] = 'у', 146 | ['Ф'] = 'ф', 147 | ['Х'] = 'х', 148 | ['Ц'] = 'ц', 149 | ['Ч'] = 'ч', 150 | ['Ш'] = 'ш', 151 | ['Щ'] = 'щ', 152 | ['Ъ'] = 'ъ', 153 | ['Э'] = 'э', 154 | ['Ь'] = 'ь', 155 | ['Ы'] = 'ы', 156 | ['Ю'] = 'ю', 157 | ['Я'] = 'я', 158 | } 159 | 160 | local function use_text_event(key) 161 | if key == "return" or key == "space" or key == "backspace" then 162 | return false 163 | end 164 | return instead.text_input and instead.text_input() 165 | end 166 | 167 | local function tolow(s) 168 | if not s then 169 | return 170 | end 171 | s = s:lower(); 172 | local xlat = kbdlower 173 | if xlat then 174 | local k,v 175 | for k,v in pairs(xlat) do 176 | s = s:gsub(k,v); 177 | end 178 | end 179 | return s; 180 | end 181 | 182 | local function input_esc(s) 183 | local rep = function(s) 184 | return fmt.nb(s) 185 | end 186 | if not s then return end 187 | local r = s:gsub("[^ ]+", rep):gsub("[ \t]", rep):gsub("{","\\{"):gsub("}","\\}"):gsub("|", "\\|"); 188 | return r 189 | end 190 | 191 | local function kbdxlat(s, k) 192 | local kbd 193 | 194 | if k:len() > 1 then 195 | return 196 | end 197 | 198 | if s.alt_xlat and 199 | (game.codepage == 'UTF-8' or game.codepage == 'utf-8') then 200 | kbd = kbdru; 201 | else 202 | kbd = kbden 203 | end 204 | 205 | if kbd and s.shift then 206 | kbd = kbd.shifted; 207 | end 208 | 209 | if not kbd[k] then 210 | if s.shift then 211 | return k:upper(); 212 | end 213 | return k; 214 | end 215 | return kbd[k] 216 | end 217 | 218 | local hook_keys = { 219 | ['a'] = true, ['b'] = true, ['c'] = true, ['d'] = true, ['e'] = true, ['f'] = true, 220 | ['g'] = true, ['h'] = true, ['i'] = true, ['j'] = true, ['k'] = true, ['l'] = true, 221 | ['m'] = true, ['n'] = true, ['o'] = true, ['p'] = true, ['q'] = true, ['r'] = true, 222 | ['s'] = true, ['t'] = true, ['u'] = true, ['v'] = true, ['w'] = true, ['x'] = true, 223 | ['y'] = true, ['z'] = true, ['1'] = true, ['2'] = true, ['3'] = true, ['4'] = true, 224 | ['5'] = true, ['6'] = true, ['7'] = true, ['8'] = true, ['9'] = true, ['0'] = true, 225 | ["-"] = true, ["="] = true, ["["] = true, ["]"] = true, ["\\"] = true, [";"] = true, 226 | ["'"] = true, [","] = true, ["."] = true, ["/"] = true, ['space'] = true, ['backspace'] = true, ['`'] = true, 227 | ['left alt'] = true, ['right alt'] = true, ['alt'] = true, ['left shift'] = true, 228 | ['right shift'] = true, ['shift'] = true, ['return'] = true, 229 | } 230 | 231 | -- instead has function walkback() in stdlib 232 | -- but for old versions we implement own version 233 | local function walkback() 234 | local w = std.me():where():from() 235 | local o = w:from() 236 | std.walkout(w) 237 | w.__from = o 238 | end 239 | 240 | obj { 241 | nam = '@kbdinput'; 242 | text = function(s, w) 243 | std.here().text = std.here().text..w; 244 | end; 245 | act = function(s, w) 246 | if w == 'comma' then 247 | w = ',' 248 | elseif w == 'bsl' then 249 | w = '\\' 250 | end 251 | if w:find("alt") then 252 | std.here().alt_xlat = not std.here().alt_xlat 253 | return true 254 | end 255 | 256 | if w:find("shift") then 257 | std.here().shift = not std.here().shift 258 | return true 259 | end 260 | 261 | if w == 'space' then 262 | w = ' ' 263 | end 264 | if w == 'backspace' then 265 | if not std.here().text or std.here().text == '' then 266 | return 267 | end 268 | if std.here().text:byte(std.here().text:len()) >= 128 then 269 | std.here().text = std.here().text:sub(1, std.here().text:len() - 2); 270 | else 271 | std.here().text = std.here().text:sub(1, std.here().text:len() - 1); 272 | end 273 | elseif w == 'cancel' then 274 | std.here().text = ''; 275 | walkback(); 276 | elseif w == 'return' then 277 | walkback(); 278 | return std.call(std.here(), 'onkbd', _'@keyboard'.text, std.unpack(_'@keyboard'.args)) 279 | else 280 | w = kbdxlat(stead.here(), w) 281 | std.here().text = std.here().text..w; 282 | end 283 | end; 284 | } 285 | 286 | room { 287 | nam = '@keyboard'; 288 | text = ''; 289 | alt = false; 290 | numeric = false; 291 | shift = false; 292 | alt_xlat = false; 293 | noinv = true; 294 | keyboard_type = true; 295 | cursor = fmt.b '|'; 296 | title = false; 297 | args = {}; 298 | msg = fmt.b '> '; 299 | act = function(s, w, ...) 300 | s.title = w or "?" 301 | s.args = { ... } 302 | walkin(s) 303 | end; 304 | ini = function(s, load) 305 | s.alt = false 306 | if load and std.here() == s then 307 | s.__flt = instead.mouse_filter(0) 308 | end 309 | end; 310 | enter = function(s) 311 | s.text = ''; 312 | s.alt = false 313 | s.shift = false 314 | s.__flt = instead.mouse_filter(0) 315 | s.__oldquotes = fmt.quotes 316 | fmt.quotes = false 317 | end; 318 | exit = function(s) 319 | instead.mouse_filter(s.__flt) 320 | fmt.quotes = s.__oldquotes 321 | end; 322 | onkey = function(s, press, key) 323 | if not use_text_event() then 324 | if key:find("alt") then 325 | s.alt = press 326 | if not press then 327 | s.alt_xlat = not s.alt_xlat 328 | end 329 | s:decor() 330 | return false 331 | end 332 | if s.alt then 333 | return false 334 | end 335 | if key:find("shift") then 336 | s.shift = press 337 | return true 338 | end 339 | end 340 | if not press then 341 | return false 342 | end 343 | if s.alt then 344 | return false 345 | end 346 | if not use_text_event(key) then 347 | return std.call(_'@kbdinput', 'act', key); 348 | end 349 | return false 350 | end; 351 | decor = function(s) 352 | p (s.msg) 353 | p (input_esc(s.text)..s.cursor) 354 | pn() 355 | local k,v 356 | for k, v in ipairs(keyb) do 357 | local kk, vv 358 | local row = '' 359 | for kk, vv in ipairs(v) do 360 | if s.numeric and not std.tonum(vv) then 361 | break 362 | end 363 | local a = kbdxlat(s, vv) 364 | if vv == ',' then 365 | vv = 'comma' 366 | elseif vv == '\\' then 367 | vv = 'bsl' 368 | end 369 | row = row.."{@kbdinput \""..vv.."\"|"..input_esc(a).."}"..fmt.nb " "; 370 | end 371 | pn(fmt.c(row)) 372 | if s.numeric then 373 | break 374 | end 375 | end 376 | if s.numeric then 377 | pn (fmt.c[[{@kbdinput cancel|«Отмена»}    {@kbdinput backspace|«Забой»}    {@kbdinput return|«Ввод»}]]); 378 | else 379 | pn (fmt.c[[{@kbdinput alt|«Alt»}    {@kbdinput shift|«Shift»}    {@kbdinput space|«Пробел»}    {@kbdinput cancel|«Отмена»}    {@kbdinput backspace|«Забой»}    {@kbdinput return|«Ввод»}]]); 380 | end 381 | end; 382 | } 383 | 384 | local hooked 385 | local orig_filter, orig_text 386 | 387 | std.mod_start(function(load) 388 | if not hooked then 389 | hooked = true 390 | orig_filter = std.rawget(keys, 'filter') 391 | std.rawset(keys, 'filter', std.hook(keys.filter, 392 | function(f, s, press, key) 393 | if std.here().keyboard_type then 394 | if _'@keyboard'.numeric and not std.tonum(key) and key ~= 'backspace' and key ~= 'return' then 395 | return false 396 | end 397 | return hook_keys[key] 398 | end 399 | return f(s, press, key) 400 | end)) 401 | orig_text = std.rawget(input, 'text') 402 | std.rawset(input, 'text', std.hook(input.text, 403 | function(f, s, text, ...) 404 | if std.here().keyboard_type and text ~= " " and use_text_event() then 405 | return '@kbdinput text '..string.format("%q", text) 406 | end 407 | return f(s, text, ...) 408 | end)) 409 | end 410 | end) 411 | 412 | std.mod_cmd(function(cmd) 413 | if cmd[1] ~= '@kbdinput' or cmd[2] ~= 'text' then 414 | return 415 | end 416 | return std.call(_'@kbdinput', 'text', cmd[3]); 417 | end) 418 | 419 | std.mod_done(function(load) 420 | hooked = false 421 | std.rawset(keys, 'filter', orig_filter) 422 | end) 423 | 424 | keyboard = _'@keyboard' 425 | -------------------------------------------------------------------------------- /keyboard/main3.lua: -------------------------------------------------------------------------------- 1 | require "keyboard" 2 | 3 | keyboard.alt_xlat = true 4 | 5 | room { 6 | title = '?'; 7 | nam = 'main'; 8 | name = false; 9 | onkbd = function(s, w) 10 | if w == 'instead' then 11 | walk 'theend' 12 | return 13 | end 14 | s.name = w 15 | end; 16 | dsc = function(s) 17 | if s.name then 18 | p ("Привет, ", s.name) 19 | else 20 | p [[Как вас {@keyboard "Имя"|зовут}?]]; 21 | end 22 | end 23 | } 24 | 25 | room { 26 | nam = 'theend'; 27 | title = 'Конец'; 28 | dsc = [[WOW!]]; 29 | } 30 | -------------------------------------------------------------------------------- /link/link.lua: -------------------------------------------------------------------------------- 1 | require "fmt" 2 | 3 | if not instead.atleast(3, 2) then 4 | std.dprint("Warning: link module is not functional on this INSTEAD version") 5 | function instead.clipboard() 6 | return false 7 | end 8 | end 9 | 10 | obj { 11 | nam = '$link'; 12 | act = function(s, w) 13 | if instead.clipboard() ~= w then 14 | std.p ('{@link ', w, '|', w, '}') 15 | else 16 | std.p(fmt.u (w) ..' [в буфере обмена]') 17 | end 18 | end; 19 | } 20 | 21 | obj { 22 | nam = '@link'; 23 | act = function(s, w) 24 | instead.clipboard(w) 25 | end; 26 | } 27 | -------------------------------------------------------------------------------- /link/main3.lua: -------------------------------------------------------------------------------- 1 | require "fmt" 2 | 3 | loadmod "link" 4 | 5 | room { 6 | nam = 'main'; 7 | decor = [[{$link|http://instead.syscall.ru}^^ 8 | {$link|http://instead.hugeping.ru}]]; 9 | } 10 | -------------------------------------------------------------------------------- /protect/open/dec.lua.cpt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instead-hub/stead3-modules/c97f6831fb3be47f9199cacf2677265cc0596bc9/protect/open/dec.lua.cpt -------------------------------------------------------------------------------- /protect/open/enc.lua.cpt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/instead-hub/stead3-modules/c97f6831fb3be47f9199cacf2677265cc0596bc9/protect/open/enc.lua.cpt -------------------------------------------------------------------------------- /protect/protect.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | Code obfuscator 3 | 4 | Usage: 5 | lua protect.lua infile outfile 6 | Or: 7 | sdl-instead -lua protect.lua infile outfile 8 | ]]-- 9 | 10 | local eval 11 | if _VERSION ~= "Lua 5.1" then 12 | eval = load 13 | else 14 | eval = loadstring 15 | end 16 | local encoder = eval[======[local a=string.char;local type=type;local select=select;local b=string.sub;local c=table.concat;local d={}local e={}for f=0,255 do local g,h=a(f),a(f,0)d[g]=h;e[h]=g end;function add(i,j)return(i+j)%256 end;local function k(l,m,i,j)if i>=256 then i,j=0,j+1;if j>=256 then m={}j=1 end end;m[l]=a(i,j)i=i+1;return m,i,j end;local function n()local o=[=====[(function(a)local b=string.char;local type=type;local select=select;local c=string.sub;local d=table.concat;local e='Depacker v1.0'local f={}local g={}local h=e:sub(2,2)for i=0,255 do local j,k=b(i),b(i,0)f[j]=k;g[k]=j end;local l='st'local function m(n,o,p,q)if p>=256 then p,q=0,q+1;if q>=256 then o={}q=1 end end;o[b(p,q)]=n;p=p+1;return o,p,q end;h=h..e:sub(10,10)local function r(s)local t=0;local u=std.getinfo(s,"S")if u.source and string.sub(u.source,1,1)=="@"then local v=string.sub(u.source,2)local i=0;for w in io.lines(v)do i=i+1;if i>u.lastlinedefined then break end;if i>=u.linedefined then for i=1,w:len()do t=(t+string.byte(w,i))%0x100000 end end end end;return t end;h=h..e:sub(4,4)local function x(p,q)return math.floor((256+p-q)%256)end;local t=r(2)l=l..'d'local function y(z)local A=''for i=1,z:len()do A=A..string.char(x(string.byte(z,i),t))end;return A end;h=h..'l'local u=std.getinfo(2,"S")local s=io.open(u.source:sub(2),"rb")if not s then error"No file!"end;while s:read("*l"):find('--[===[',1,true)~=1 do end;a=s:read("*all"):sub(1,-10)s:close()a=y(a)if type(a)~="string"then error("string expected, got "..type(a))end;if#a<1 then error("invalid input - not a compressed string")end;local B=c(a,1,1)if B=="u"then return _(c(a,2))elseif B~="c"then error"invalid input - not a compressed string"end;a=c(a,2)local C=#a;local _=_G[l][h]if C<2 then error"invalid input - not a compressed string"end;local o={}local p,q=0,1;local D={}local E=1;local F=c(a,1,2)D[E]=g[F]or o[F]E=E+1;for i=3,C,2 do local G=c(a,i,i+1)local H=g[F]or o[F]if not H then error"could not find last from dict. Invalid input?"end;local I=g[G]or o[G]if I then D[E]=I;E=E+1;o,p,q=m(H..c(I,1,1),o,p,q)else local J=H..c(H,1,1)D[E]=J;E=E+1;o,p,q=m(J,o,p,q)end;F=G end;_(d(D))()end)()]=====]local p=0;local q=string.format("local IiIiI='%d'",math.random(0x10000000))local r=''o=o:gsub("^%(function%(([^)]+)%)","(function(%1)"..q)for f=1,o:len()do p=(p+string.byte(o,f))%0x100000 end;r=r..o..'\n'return p,r end;local function s(t)if type(t)~="string"then error("string expected, got "..type(t))end;local p,r=n()local u=#t;if u<=1 then error"Len is too small"end;local m={}local i,j=0,1;local v={"c"}local w=1;local x=2;local y=""local function z(A)local o=''for f=1,A:len()do o=o..string.char(add(string.byte(A,f),p))end;return o end;for f=1,u do local B=b(t,f,f)local C=y..B;if not(d[C]or m[C])then local D=d[y]or m[y]if not D then error"algorithm error, could not fetch word"end;v[x]=D;w=w+#D;x=x+1;m,i,j=k(C,m,i,j)y=B else y=C end end;v[x]=d[y]or m[y]w=w+#v[x]x=x+1;return z(c(v)),r end;return{compress=s}]======]() 17 | 18 | function encode(file, ofile) 19 | local f, e = io.open(file, "rb") 20 | if not f then 21 | error(e) 22 | end 23 | local data = '' 24 | local h = '' 25 | for l in f:lines() do 26 | data = data .. l .. '\n' 27 | if not l:find("^[ \t]*%-%-") and not l:find("^[ \t]*$") then 28 | break 29 | end 30 | h = h .. l..'\n' 31 | end 32 | data = data .. f:read("*all") 33 | f:close() 34 | 35 | local compressed, app = encoder.compress(data) 36 | 37 | local o, e = io.open(ofile, "wb") 38 | if not o then 39 | error(e) 40 | end 41 | o:write(app) 42 | o:write("--[===[\n") 43 | o:write(compressed) 44 | o:write("\n]===]--\n") 45 | o:close() 46 | end 47 | local args = {...} 48 | 49 | if #args < 2 then 50 | print("Usage: ") 51 | os.exit(1) 52 | end 53 | 54 | encode(args[1], args[2]) 55 | -------------------------------------------------------------------------------- /proxymenu/main3.lua: -------------------------------------------------------------------------------- 1 | require "noinv" 2 | 3 | loadmod 'proxymenu' 4 | 5 | 6 | game.useit = 'Не помогло.' 7 | game.use = 'Не сработает.' 8 | game.give = function(s, w, ww) 9 | p 'Отдать? Ни за что!' 10 | p (w, "->", ww) 11 | end 12 | game.eat = 'Не буду это есть.' 13 | game.drop = 'Еще пригодится.' 14 | game.exam = 'Ничего необычного.' 15 | game.take = 'Стоит ли это брать?' 16 | game.push = 'Ничего не произошло.' 17 | 18 | game.after_take = function(s, w) 19 | take(w) 20 | end 21 | 22 | game.after_drop = function(s, w) 23 | drop(w) 24 | end 25 | 26 | obj { 27 | nam = 'ножик', 28 | dsc = 'На полу валяется ножик.', 29 | exam = 'Бесполезный перочинный ножик.', 30 | useon = function(s, w) 31 | return 'Вы пытаетесь юзать нож на объект: '..std.dispof(w)..'. Получилось!' 32 | end, 33 | } 34 | xact.walk = walk 35 | room { 36 | nam = 'main', 37 | title = false, 38 | noinv = true; 39 | decor = [[Введение. {@ walk "r1"|Дальше}]]; 40 | exit = function(s) 41 | take 'ножик' 42 | end, 43 | } 44 | 45 | obj { 46 | nam = 'куб', 47 | dsc = 'В центре комнаты находится куб.', 48 | take = 'Вы взяли куб', 49 | exam = 'Мультифункциональный куб -- написано на кубе.', 50 | drop = 'Вы положили куб.', 51 | useit = 'Как можно использовать куб?', 52 | talk = 'Вы поговорили с кубом.', 53 | eat = function(s) 54 | return 'Вы не можете разгрызть куб.', false; 55 | end, 56 | openit = 'Вы открыли куб.', 57 | closeit = 'Вы закрыли куб.', 58 | push = 'Вы толкаете куб.', 59 | give = function(s, w) 60 | return 'Вы пытаетесь отдать куб объекту: '..std.dispof(w)..'.', false 61 | end, 62 | useon = function(s, w) 63 | return 'Вы пытаетесь юзать куб на объект: '..std.dispof(w)..'. Получилось!' 64 | end, 65 | -- used = 'Куб поюзан.', 66 | }; 67 | 68 | obj { 69 | nam = 'сфера', 70 | dsc = 'В центре комнаты находится сфера.', 71 | take = 'Вы взяли сферу', 72 | exam = 'Мультифункциональная сфера -- написано на сфере.', 73 | drop = 'Вы положили сферу.', 74 | useit = 'Как можно использовать сферу?', 75 | talk = 'Вы поговорили с сферой.', 76 | eat = function(s) 77 | return 'Вы не можете разгрызть сферу.', false; 78 | end, 79 | openit= 'Вы открыли сферу.', 80 | closeit = 'Вы закрыли сферу.', 81 | push = 'Вы толкаете сферу.', 82 | give = function(s, w) 83 | return 'Вы пытаетесь отдать сферу объекту: '..std.dispof(w)..'.', false 84 | end, 85 | useon = function(s, w) 86 | return 'Вы пытаетесь юзать сферу на объект: '..std.dispof(w)..'. Получилось!' 87 | end, 88 | -- used = 'Сфера поюзана.', 89 | }; 90 | 91 | room { 92 | nam = 'r1', 93 | title = false; 94 | dsc = 'Вы в комнате', 95 | obj = { 'куб', 'сфера' }, 96 | } 97 | 98 | game.player = std.menu_player { nam = 'player' } 99 | 100 | place( proxy_menu { 101 | disp = 'С СОБОЙ', 102 | acts = { inv = 'exam' }; 103 | sources = { inv = true }; 104 | }, me()) 105 | 106 | place( proxy_menu { 107 | disp = 'ОСМОТРЕТЬ'; 108 | acts = { inv = 'exam' }; 109 | sources = { scene = true }; 110 | }, me()) 111 | 112 | place( proxy_menu { 113 | disp = 'ВЗЯТЬ'; 114 | acts = { inv = 'take' }; 115 | sources = { scene = true }; 116 | }, me()) 117 | 118 | place( proxy_menu { 119 | disp = 'БРОСИТЬ'; 120 | acts = { inv = 'drop' }; 121 | sources = { inv = true }; 122 | }, me()) 123 | 124 | place( proxy_menu { 125 | disp = 'ЕСТЬ'; 126 | acts = { inv = 'eat' }; 127 | sources = { inv = true }; 128 | }, me()) 129 | 130 | place( proxy_menu { 131 | disp = 'ТОЛКАТЬ'; 132 | acts = { inv = 'push' }; 133 | sources = { scene = true }; 134 | }, me()) 135 | 136 | place( proxy_menu { 137 | disp = 'ИСПОЛЬЗОВАТЬ'; 138 | use_mode = true; 139 | acts = { use = 'useon', used = 'used', inv = 'useit' }; 140 | sources = { inv = true, scene = true }; 141 | }, me()) 142 | 143 | place( proxy_menu { 144 | disp = 'ОТДАТЬ'; 145 | use_mode = true; 146 | acts = { use = 'give', used = 'accept' }; 147 | sources = { inv = true, scene = true }; 148 | }, me()) 149 | 150 | place( proxy_menu { 151 | disp = 'ИДТИ'; 152 | acts = { inv = 'walk' }; 153 | sources = { ways = true }; 154 | }, me()) 155 | 156 | 157 | function init() 158 | instead.noways = true 159 | 160 | end 161 | -------------------------------------------------------------------------------- /proxymenu/proxymenu.lua: -------------------------------------------------------------------------------- 1 | require "fmt" 2 | 3 | stead.proxy_prefix = '   ' 4 | 5 | local function proxy_wrap(nam, fwd) 6 | if not fwd then fwd = nam end 7 | return function(s, ...) 8 | local t 9 | local o = _(s.ref) 10 | local act = s.acts or { } 11 | local par = { ... } 12 | 13 | act = act[nam] or nam 14 | 15 | if nam == 'use' then 16 | local oo = par[1] 17 | if oo:type 'proxy' then 18 | oo = _(oo.ref) 19 | par[1] = oo 20 | end 21 | end 22 | 23 | local r, v = std.call(std.game, 'before_'..act, o, std.unpack(par)) 24 | t = std.par(std.scene_delim, t or false, r) 25 | if v == false then 26 | return t or r, true 27 | end 28 | 29 | if nam == 'use' then 30 | r, v = std.call(par[1], s.acts.used or 'used', o) 31 | t = std.par(std.scene_delim, t or false, r) 32 | if v == true then 33 | oo['__nr_used'] = (oo['__nr_used'] or 0) + 1 34 | return t or r, true 35 | end 36 | end 37 | 38 | r, v = std.call(o, act, std.unpack(par)) 39 | 40 | t = std.par(std.scene_delim, t or false, r) 41 | 42 | if type(v) == 'boolean' then 43 | o['__nr_'..act] = (o['__nr_'..act] or 0) + 1 44 | end 45 | 46 | if r ~= nil and v == false then -- deny 47 | return t or r, true 48 | end 49 | 50 | if v then 51 | r, v = std.call(std.game, 'after_'..act, o, std.unpack(par)) 52 | t = std.par(std.scene_delim, t or false, r) 53 | else -- game action 54 | r, v = std.call(game, act, o, std.unpack(par)) 55 | t = std.par(std.scene_delim, t or false, r) 56 | end 57 | return t or r, true 58 | end 59 | end 60 | 61 | std.proxy_obj = std.class ({ 62 | __proxy_type = true; 63 | new = function(s, v) 64 | if type(v) ~= 'table' then 65 | std.err ("Wrong argument to std.proxy_obj: "..std.tostr(v), 2) 66 | end 67 | if not v.ref then 68 | std.err ("Wrong argument to std.proxy_obj (no ref attr): "..std.tostr(v), 2) 69 | end 70 | if v.use_mode then 71 | v.__menu_type = false 72 | end 73 | v = std.obj (v) 74 | return v 75 | end; 76 | disp = function(s) 77 | local o = _(s.ref) 78 | local d = std.dispof(o) 79 | if type(d) ~= 'string' then 80 | return d 81 | end 82 | if have(o) then 83 | return stead.proxy_prefix..fmt.em(d) 84 | end 85 | return stead.proxy_prefix..d 86 | end; 87 | act = proxy_wrap ('act'); 88 | inv = proxy_wrap ('inv'); 89 | use = proxy_wrap ('use'); 90 | menu = proxy_wrap ('menu'); 91 | tak = proxy_wrap ('tak'); 92 | }, std.menu) 93 | 94 | local function fill_obj(v, s) 95 | if not v:visible() then 96 | return nil, false -- do not do it recurse 97 | end 98 | if not v:type 'menu' and not std.is_system(v) then -- usual objects 99 | -- add to proxy 100 | local o = { 101 | ref = std.nameof(v), 102 | use_mode = s.use_mode, 103 | sources = s.sources, 104 | acts = s.acts, 105 | } 106 | s.obj:add(new(proxy_obj, o)) 107 | end 108 | if v:closed() then 109 | return nil, false -- do not do it recurse 110 | end 111 | end 112 | 113 | std.proxy_menu = std.class ({ 114 | __proxy_menu_type = true; 115 | new = function(s, v) 116 | if type(v) ~= 'table' then 117 | std.err ("Wrong argument to std.proxy_obj: "..std.tostr(v), 2) 118 | end 119 | if v.disp then 120 | v.title = v.disp 121 | v.disp = nil 122 | end 123 | return std.menu(v) 124 | end; 125 | disp = function(s) 126 | local d 127 | if s.title then 128 | d = std.call(s, 'title') 129 | else 130 | d = std.dispof(s) 131 | end 132 | -- s:fill() 133 | if not s:closed() then 134 | return fmt.u(fmt.b(fmt.nb(d))) 135 | else 136 | return fmt.b(fmt.nb(d)) 137 | end 138 | end; 139 | menu = function(s) -- open/close 140 | if not s:closed() then 141 | s:close() 142 | else 143 | std.me().obj:for_each(function (v) 144 | if v:type 'proxy_menu' and v ~= s then 145 | v:close() 146 | end 147 | end) 148 | s:open() 149 | end 150 | return false 151 | end; 152 | fill = function(s) -- fill prox 153 | s:for_each(function(v) 154 | delete(v) -- delete obj 155 | end) 156 | s.obj:zap() 157 | -- by default -- all obj 158 | local src = s.sources or { scene = true } 159 | 160 | if src.inv then 161 | me():inventory():for_each(function(v) 162 | fill_obj(v, s) 163 | end) 164 | end 165 | 166 | if src.scene then 167 | std.here():for_each(function(v) 168 | return fill_obj(v, s) 169 | end) 170 | end 171 | 172 | if src.ways then 173 | std.here().way:for_each(function(v) 174 | fill_obj(v, s) 175 | end) 176 | end 177 | 178 | end; 179 | }, std.menu) 180 | 181 | std.menu_player = std.class ({ 182 | __menu_player_type = true; 183 | new = function(self, v) 184 | if type(v) ~= 'table' then 185 | std.err ("Wrong argument to std.menu_player: "..std.tostr(v), 2) 186 | end 187 | if not v.nam then 188 | v.nam = 'menu_player' 189 | end 190 | if not v.room then 191 | v.room = 'main' 192 | end 193 | v.invent = std.list {} 194 | return std.player(v) 195 | end; 196 | inventory = function(s) 197 | return s.invent 198 | end; 199 | }, std.player) 200 | 201 | function proxy_obj(v) 202 | local vv = { 203 | ref = v.ref; 204 | use_mode = v.use_mode; 205 | sources = v.sources; 206 | acts = v.acts; 207 | } 208 | return std.proxy_obj(vv) 209 | end 210 | 211 | function proxy_menu(v) 212 | local vv = { 213 | nam = v.nam; 214 | disp = v.disp; 215 | use_mode = v.use_mode; 216 | sources = v.sources; 217 | acts = v.acts; 218 | } 219 | return std.proxy_menu(vv):close() 220 | end 221 | 222 | std.mod_init(function() -- declarations 223 | declare 'proxy_obj' (proxy_obj) 224 | declare 'proxy_menu' (proxy_menu) 225 | end) 226 | 227 | std.mod_step(function(state) 228 | if not state then 229 | return 230 | end 231 | me().obj:for_each(function(v) 232 | if v:type 'proxy_menu' then 233 | v:fill() 234 | end 235 | end) 236 | end) 237 | -------------------------------------------------------------------------------- /quake/main3.lua: -------------------------------------------------------------------------------- 1 | loadmod 'quake' 2 | 3 | obj { 4 | nam = 'бомба'; 5 | dsc = [[На полу лежит {бомба}.]]; 6 | act = function(s) 7 | p [[БАХ!!!]]; 8 | quake.start() 9 | -- remove(s) 10 | end; 11 | } 12 | 13 | room { 14 | nam = 'main'; 15 | dsc = [[Комната.]]; 16 | obj = { 'бомба' }; 17 | } 18 | -------------------------------------------------------------------------------- /quake/quake.lua: -------------------------------------------------------------------------------- 1 | require "sprite" 2 | require "theme" 3 | require "timer" 4 | 5 | local q = std.obj { 6 | { 7 | started = false; 8 | timer = false; 9 | step = 0; 10 | }; 11 | max = 3; -- iterations 12 | power = 30; -- power 13 | post = true; -- after action or before it 14 | nam = '@quake'; 15 | } 16 | 17 | local scr 18 | local cb = timer.callback 19 | 20 | function timer:callback(...) 21 | if q.started then 22 | return '@quake' 23 | end 24 | return cb(self, ...) 25 | end 26 | 27 | function q.start() 28 | local old = sprite.direct() 29 | sprite.direct(true) 30 | sprite.scr():copy(scr) 31 | sprite.direct(old) 32 | q.timer = timer:get() 33 | q.step = 0 34 | q.started = true 35 | timer:set(50) 36 | if not q.post then 37 | sprite.direct(true) 38 | end 39 | end 40 | 41 | std.mod_cmd(function(cmd) 42 | if cmd[1] ~= '@quake' then 43 | return 44 | end 45 | if not sprite.direct() then 46 | sprite.direct(true) 47 | sprite.scr():copy(scr) 48 | end 49 | q.step = q.step + 1 50 | sprite.scr():fill('black') 51 | scr:copy(sprite.scr(), rnd(q.power) - q.power / 2, rnd(q.power) - q.power / 2); 52 | if q.step > q.max then 53 | q.started = false 54 | timer:set(q.timer) 55 | sprite.direct(false) 56 | return std.nop() 57 | end 58 | return false 59 | end) 60 | 61 | std.mod_start(function() 62 | scr = sprite.new(theme.get 'scr.w', theme.get 'scr.h') 63 | end) 64 | 65 | quake = q 66 | -------------------------------------------------------------------------------- /sfxr/main3.lua: -------------------------------------------------------------------------------- 1 | require "snd" 2 | require "timer" 3 | require "fmt" 4 | 5 | local sfxr = require("sfxr") 6 | local last = true 7 | local wav -- to cache sound 8 | 9 | room { 10 | nam = 'main'; 11 | timer = function() 12 | if snd.playing() then 13 | p [[PLAYING]] 14 | last = true 15 | return 16 | elseif last then 17 | last = false 18 | pn [[Нажмите на {button|кнопку} для эффекта.]]; 19 | p (fmt.em [[Внимание! Программа генерирует случайный звук, который может оказаться слишком громким!]]) 20 | end 21 | return false 22 | end 23 | }:with{ 24 | obj { 25 | nam = 'button'; 26 | act = function(s) 27 | local sound = sfxr.newSound() 28 | sound:randomize(rnd(32768)) 29 | local sounddata = sound:generateSoundData(22050) 30 | wav = snd.new(22050, 1, sounddata) 31 | wav:play() 32 | end 33 | } 34 | } 35 | 36 | function start() 37 | timer:set(100) 38 | end 39 | -------------------------------------------------------------------------------- /sfxr/sfxr.lua: -------------------------------------------------------------------------------- 1 | -- sfxr.lua 2 | -- original by Tomas Pettersson, ported to Lua by nucular 3 | 4 | --[[ 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | ]]-- 23 | 24 | --[[-- 25 | A port of the sfxr sound effect synthesizer to pure Lua, designed to be used 26 | together with the *awesome* [LÖVE](https://love2d.org) game framework. 27 | ]]-- 28 | 29 | --[[-- 30 | Adopted to work with [INSTEAD](http://instead.syscall.ru) in 2016 by Peter Kosyh 31 | ]]-- 32 | 33 | -- @module sfxr 34 | local sfxr = {} 35 | 36 | -- Constants 37 | 38 | --- The module version (SemVer format) 39 | -- @within Constants 40 | sfxr.VERSION = "0.0.2" 41 | 42 | --- [Waveform](https://en.wikipedia.org/wiki/Waveform) constants 43 | -- @within Constants 44 | -- @field SQUARE [square wave](https://en.wikipedia.org/wiki/Square_wave) (`= 0`) 45 | -- @field SAW [saw wave](https://en.wikipedia.org/wiki/Sawtooth_wave) (`= 1`) 46 | -- @field SINE [sine wave](https://en.wikipedia.org/wiki/Sine_wave) (`= 2`) 47 | -- @field NOISE [white noise](https://en.wikipedia.org/wiki/White_noise) (`= 3`) 48 | sfxr.WAVEFORM = { 49 | SQUARE = 0, 50 | [0] = 0, 51 | SAW = 1, 52 | [1] = 1, 53 | SINE = 2, 54 | [2] = 2, 55 | NOISE = 3, 56 | [3] = 3 57 | } 58 | 59 | --- [Sampling rate](https://en.wikipedia.org/wiki/Sampling_(signal_processing)#Sampling_rate) constants 60 | -- (use the number values directly, these are just for lookup) 61 | -- @within Constants 62 | -- @field 22050 22.05 kHz (`= 22050`) 63 | -- @field 44100 44.1 kHz (`= 44100`) 64 | sfxr.SAMPLERATE = { 65 | [22050] = 22050, --- 22.05 kHz 66 | [44100] = 44100 --- 44.1 kHz 67 | } 68 | 69 | --- [Bit depth](https://en.wikipedia.org/wiki/Audio_bit_depth) constants 70 | -- (use the number values directly, these are just for lookup) 71 | -- @within Constants 72 | -- @field 0 floating point bit depth, -1 to 1 (`= 0`) 73 | -- @field 8 unsigned 8 bit, 0x00 to 0xFF (`= 8`) 74 | -- @field 16 unsigned 16 bit, 0x0000 to 0xFFFF (`= 16`) 75 | sfxr.BITDEPTH = { 76 | [0] = 0, 77 | [16] = 16, 78 | [8] = 8 79 | } 80 | 81 | --- [Endianness](https://en.wikipedia.org/wiki/Endianness) constants 82 | -- @within Constants 83 | -- @field LITTLE little endian (`= 0`) 84 | -- @field BIG big endian (`= 1`) 85 | sfxr.ENDIANNESS = { 86 | LITTLE = 0, 87 | [0] = 0, 88 | BIG = 1, 89 | [1] = 1 90 | } 91 | 92 | -- Utilities 93 | 94 | --- Truncate a number to an unsigned integer. 95 | -- @tparam number n a (signed) number 96 | -- @treturn int the number, truncated and unsigned 97 | local function trunc(n) 98 | if n >= 0 then 99 | return math.floor(n) 100 | else 101 | return -math.floor(-n) 102 | end 103 | end 104 | 105 | --- Set the random seed and initializes the generator. 106 | -- @tparam number seed the random seed 107 | local function setseed(seed) 108 | math.randomseed(seed) 109 | for i=0, 5 do 110 | math.random() 111 | end 112 | end 113 | 114 | --- Return a random number between low and high. 115 | -- @tparam number low the lower bound 116 | -- @tparam number high the upper bound 117 | -- @treturn number a random number where `low < n < high` 118 | local function random(low, high) 119 | return low + math.random() * (high - low) 120 | end 121 | 122 | --- Return a random boolean weighted towards false by n. 123 | -- w = 1: uniform distribution 124 | -- w = n: false is n times as likely as true 125 | -- Note: n < 0 do not work, use `not maybe(w)` instead 126 | -- @tparam[opt=1] number w the weight towards false 127 | -- @treturn bool a random boolean 128 | local function maybe(w) 129 | return trunc(random(0, w or 1)) == 0 130 | end 131 | 132 | --- Clamp n between min and max. 133 | -- @tparam number n the number 134 | -- @tparam number min the lower bound 135 | -- @tparam number max the upper bound 136 | -- @treturn number the number where `min <= n <= max` 137 | local function clamp(n, min, max) 138 | return math.max(min or -math.huge, math.min(max or math.huge, n)) 139 | end 140 | 141 | --- Copy a table (shallow) or a primitive. 142 | -- @param t a table or primitive 143 | -- @return a copy of t 144 | local function shallowcopy(t) 145 | if type(t) == "table" then 146 | local t2 = {} 147 | for k,v in pairs(t) do 148 | t2[k] = v 149 | end 150 | return t2 151 | else 152 | return t 153 | end 154 | end 155 | 156 | --- Recursively merge table t2 into t1. 157 | -- @tparam tab t1 a table 158 | -- @tparam tab t2 a table to merge into t1 159 | -- @treturn tab t1 160 | local function mergetables(t1, t2) 161 | for k, v in pairs(t2) do 162 | if type(v) == "table" then 163 | if type(t1[k] or false) == "table" then 164 | mergetables(t1[k] or {}, t2[k] or {}) 165 | else 166 | t1[k] = v 167 | end 168 | else 169 | t1[k] = v 170 | end 171 | end 172 | return t1 173 | end 174 | 175 | --- Pack a number into a IEEE754 32-bit big-endian floating point binary string. 176 | -- [source](https://stackoverflow.com/questions/14416734/) 177 | -- @tparam number number a number 178 | -- @treturn string a binary string 179 | local function packIEEE754(number) 180 | if number == 0 then 181 | return string.char(0x00, 0x00, 0x00, 0x00) 182 | elseif number ~= number then 183 | return string.char(0xFF, 0xFF, 0xFF, 0xFF) 184 | else 185 | local sign = 0x00 186 | if number < 0 then 187 | sign = 0x80 188 | number = -number 189 | end 190 | local mantissa, exponent = math.frexp(number) 191 | exponent = exponent + 0x7F 192 | if exponent <= 0 then 193 | mantissa = math.ldexp(mantissa, exponent - 1) 194 | exponent = 0 195 | elseif exponent > 0 then 196 | if exponent >= 0xFF then 197 | return string.char(sign + 0x7F, 0x80, 0x00, 0x00) 198 | elseif exponent == 1 then 199 | exponent = 0 200 | else 201 | mantissa = mantissa * 2 - 1 202 | exponent = exponent - 1 203 | end 204 | end 205 | mantissa = math.floor(math.ldexp(mantissa, 23) + 0.5) 206 | return string.char( 207 | sign + math.floor(exponent / 2), 208 | (exponent % 2) * 0x80 + math.floor(mantissa / 0x10000), 209 | math.floor(mantissa / 0x100) % 0x100, 210 | mantissa % 0x100) 211 | end 212 | end 213 | 214 | --- Unpack a IEEE754 32-bit big-endian floating point string to a number. 215 | -- [source](https://stackoverflow.com/questions/14416734/) 216 | -- @tparam string packed a binary string 217 | -- @treturn number a number 218 | local function unpackIEEE754(packed) 219 | local b1, b2, b3, b4 = string.byte(packed, 1, 4) 220 | local exponent = (b1 % 0x80) * 0x02 + math.floor(b2 / 0x80) 221 | local mantissa = math.ldexp(((b2 % 0x80) * 0x100 + b3) * 0x100 + b4, -23) 222 | if exponent == 0xFF then 223 | if mantissa > 0 then 224 | return 0 / 0 225 | else 226 | mantissa = math.huge 227 | exponent = 0x7F 228 | end 229 | elseif exponent > 0 then 230 | mantissa = mantissa + 1 231 | else 232 | exponent = exponent + 1 233 | end 234 | if b1 >= 0x80 then 235 | mantissa = -mantissa 236 | end 237 | return math.ldexp(mantissa, exponent - 0x7F) 238 | end 239 | 240 | --- Construct and return a new @{Sound} instance. 241 | -- @treturn Sound a Sound instance 242 | function sfxr.newSound(...) 243 | local instance = setmetatable({}, sfxr.Sound) 244 | instance:__init(...) 245 | return instance 246 | end 247 | 248 | --- The main Sound class. 249 | -- @type Sound 250 | sfxr.Sound = {} 251 | sfxr.Sound.__index = sfxr.Sound 252 | 253 | --- Initialize the Sound instance. 254 | -- Called by @{sfxr.newSound|the constructor}. 255 | function sfxr.Sound:__init() 256 | --- Number of supersampling passes to perform (*default* 8) 257 | -- @within Parameters 258 | self.supersampling = 8 259 | --- Repeat speed: 260 | -- Times to repeat the frequency slide over the course of the envelope 261 | -- (*default* 0.0, *min* 0.0, *max* 1.0) 262 | -- @within Parameters 263 | self.repeatspeed = 0.0 264 | --- The base @{WAVEFORM|waveform} (*default* @{WAVEFORM|SQUARE}) 265 | -- @within Parameters 266 | self.waveform = sfxr.WAVEFORM.SQUARE 267 | 268 | -- Build tables to store the parameters in 269 | 270 | --- **The sound volume and gain all samples are multiplied with** 271 | -- @within Volume 272 | self.volume = {} 273 | --- **The [ASD envelope](https://en.wikipedia.org/wiki/Synthesizer#Attack_ 274 | --Decay_Sustain_Release_.28ADSR.29_envelope) that controls the sound 275 | -- amplitude (volume) over time** 276 | -- @within Envelope 277 | self.envelope = {} 278 | --- **The base and minimum frequencies of the tone generator and their 279 | -- slides** 280 | -- @within Frequency 281 | self.frequency = {} 282 | --- **A [vibrato](https://en.wikipedia.org/wiki/Vibrato)-like amplitude 283 | -- modulation effect** 284 | -- SerializationVibrato 285 | self.vibrato = {} 286 | --- **Changes the frequency mid-sound to create a characteristic 287 | -- "coin"-effect** 288 | -- @within Change 289 | self.change = {} 290 | --- **The [duty](https://en.wikipedia.org/wiki/Duty_cycle) of the square 291 | -- waveform** 292 | -- @within Duty 293 | self.duty = {} 294 | --- **A simple [phaser](https://en.wikipedia.org/wiki/Phaser_(effect)) 295 | -- effect** 296 | -- @within Phaser 297 | self.phaser = {} 298 | --- **A [lowpass filter](https://en.wikipedia.org/wiki/Low-pass_filter) 299 | -- effect** 300 | -- @within Lowpass 301 | self.lowpass = {} 302 | --- **A [highpass filter](https://en.wikipedia.org/wiki/High-pass_filter) 303 | -- effect** 304 | -- @within Highpass 305 | self.highpass = {} 306 | 307 | -- These are not affected by resetParameters() 308 | 309 | --- Master volume (*default* 0.5) 310 | -- @within Volume 311 | self.volume.master = 0.5 312 | --- Additional gain (*default* 0.5) 313 | -- @within Volume 314 | self.volume.sound = 0.5 315 | 316 | self:resetParameters() 317 | end 318 | 319 | --- Set all parameters to their default values. Does not affect 320 | -- @{self.supersampling|supersampling} and @{self.volume|volume}. 321 | -- Called by @{sfxr.Sound:__init|the initializer}. 322 | function sfxr.Sound:resetParameters() 323 | self.repeatspeed = 0.0 324 | self.waveform = sfxr.WAVEFORM.SQUARE 325 | 326 | --- Attack time: 327 | -- Time the sound takes to reach its peak amplitude 328 | -- (*default* 0.0, *min* 0.0, *max* 1.0) 329 | -- @within Envelope 330 | self.envelope.attack = 0.0 331 | --- Sustain time: 332 | -- Time the sound stays on its peak amplitude 333 | -- (*default* 0.0, *min* 0.0, *max* 1.0) 334 | -- @within Envelope 335 | self.envelope.sustain = 0.3 336 | --- Sustain punch: 337 | -- Amount by which the sound peak amplitude is increased at the start of the 338 | -- sustain time 339 | -- (*default* 0.0, *min* 0.0, *max* 1.0) 340 | -- @within Envelope 341 | self.envelope.punch = 0.0 342 | --- Decay time: 343 | -- Time the sound takes to decay after its sustain time 344 | -- (*default* 0.0, *min* 0.0, *max* 1.0) 345 | -- @within Envelope 346 | self.envelope.decay = 0.4 347 | 348 | --- Start frequency: 349 | -- Base tone of the sound, before sliding 350 | -- (*default* 0.0, *min* 0.0, *max* 1.0) 351 | -- @within Frequency 352 | self.frequency.start = 0.3 353 | --- Min frequency: 354 | -- Tone below which the sound will get cut off 355 | -- (*default* 0.0, *min* 0.0, *max* 1.0) 356 | -- @within Frequency 357 | self.frequency.min = 0.0 358 | --- Slide: 359 | -- Amount by which the frequency is increased or decreased over time 360 | -- (*default* 0.0, *min* -1.0, *max* 1.0) 361 | -- @within Frequency 362 | self.frequency.slide = 0.0 363 | --- Delta slide: 364 | -- Amount by which the slide is increased or decreased over time 365 | -- (*default* 0.0, *min* -1.0, *max* 1.0) 366 | -- @within Frequency 367 | self.frequency.dslide = 0.0 368 | 369 | --- Vibrato depth: 370 | -- Amount of amplitude modulation 371 | -- (*default* 0.0, *min* 0.0, *max* 1.0) 372 | -- @within Vibrato 373 | self.vibrato.depth = 0.0 374 | --- Vibrato speed: 375 | -- Oscillation speed of the vibrato 376 | -- (*default* 0.0, *min* 0.0, *max* 1.0) 377 | -- @within Vibrato 378 | self.vibrato.speed = 0.0 379 | --- Vibrato delay: 380 | -- Unused and unimplemented 381 | -- (*default* 0.0, *min* 0.0, *max* 1.0) 382 | -- @within Vibrato 383 | self.vibrato.delay = 0.0 384 | 385 | --- Change amount: 386 | -- Amount by which the frequency is changed mid-sound 387 | -- (*default* 0.0, *min* -1.0, *max* 1.0) 388 | -- @within Change 389 | self.change.amount = 0.0 390 | --- Change speed: 391 | -- Time before the frequency change happens 392 | -- (*default* 0.0, *min* 0.0, *max* 1.0) 393 | -- @within Change 394 | self.change.speed = 0.0 395 | 396 | --- Square duty: 397 | -- Width of the square wave pulse cycle (doesn't affect other waveforms) 398 | -- (*default* 0.0, *min* 0.0, *max* 1.0) 399 | -- @within Duty 400 | self.duty.ratio = 0.0 401 | --- Duty sweep: 402 | -- Amount by which the square duty is increased or decreased over time 403 | -- (*default* 0.0, *min* -1.0, *max* 1.0) 404 | -- @within Duty 405 | self.duty.sweep = 0.0 406 | 407 | --- Phaser offset: 408 | -- Amount by which the phaser signal is offset from the sound 409 | -- (*default* 0.0, *min* -1.0, *max* 1.0) 410 | -- @within Phaser 411 | self.phaser.offset = 0.0 412 | --- Phaser sweep: 413 | -- Amount by which the phaser offset is increased or decreased over time 414 | -- (*default* 0.0, *min* -1.0, *max* 1.0) 415 | -- @within Phaser 416 | self.phaser.sweep = 0.0 417 | 418 | --- Lowpass filter cutoff: 419 | -- Lower bound for frequencies allowed to pass through this filter 420 | -- (*default* 0.0, *min* 0.0, *max* 1.0) 421 | -- @within Lowpass 422 | self.lowpass.cutoff = 1.0 423 | --- Lowpass filter cutoff sweep: 424 | -- Amount by which the LP filter cutoff is increased or decreased 425 | -- over time 426 | -- (*default* 0.0, *min* -1.0, *max* 1.0) 427 | -- @within Lowpass 428 | self.lowpass.sweep = 0.0 429 | --- Lowpass filter resonance: 430 | -- Amount by which certain resonant frequencies near the cutoff are 431 | -- increased 432 | -- (*default* 0.0, *min* 0.0, *max* 1.0) 433 | -- @within Lowpass 434 | self.lowpass.resonance = 0.0 435 | --- Highpass filter cutoff: 436 | -- Upper bound for frequencies allowed to pass through this filter 437 | -- (*default* 0.0, *min* 0.0, *max* 1.0) 438 | -- @within Highpass 439 | self.highpass.cutoff = 0.0 440 | --- Highpass filter cutoff sweep: 441 | -- Amount by which the HP filter cutoff is increased or decreased 442 | -- over time 443 | -- (*default* 0.0, *min* -1.0, *max* 1.0) 444 | -- @within Highpass 445 | self.highpass.sweep = 0.0 446 | end 447 | 448 | --- Clamp all parameters within their sane ranges. 449 | function sfxr.Sound:sanitizeParameters() 450 | self.repeatspeed = clamp(self.repeatspeed, 0, 1) 451 | self.waveform = clamp(self.waveform, 0, #sfxr.WAVEFORM) 452 | 453 | self.envelope.attack = clamp(self.envelope.attack, 0, 1) 454 | self.envelope.sustain = clamp(self.envelope.sustain, 0, 1) 455 | self.envelope.punch = clamp(self.envelope.punch, 0, 1) 456 | self.envelope.decay = clamp(self.envelope.decay, 0, 1) 457 | 458 | self.frequency.start = clamp(self.frequency.start, 0, 1) 459 | self.frequency.min = clamp(self.frequency.min, 0, 1) 460 | self.frequency.slide = clamp(self.frequency.slide, -1, 1) 461 | self.frequency.dslide = clamp(self.frequency.dslide, -1, 1) 462 | 463 | self.vibrato.depth = clamp(self.vibrato.depth, 0, 1) 464 | self.vibrato.speed = clamp(self.vibrato.speed, 0, 1) 465 | self.vibrato.delay = clamp(self.vibrato.delay, 0, 1) 466 | 467 | self.change.amount = clamp(self.change.amount, -1, 1) 468 | self.change.speed = clamp(self.change.speed, 0, 1) 469 | 470 | self.duty.ratio = clamp(self.duty.ratio, 0, 1) 471 | self.duty.sweep = clamp(self.duty.sweep, -1, 1) 472 | 473 | self.phaser.offset = clamp(self.phaser.offset, -1, 1) 474 | self.phaser.sweep = clamp(self.phaser.sweep, -1, 1) 475 | 476 | self.lowpass.cutoff = clamp(self.lowpass.cutoff, 0, 1) 477 | self.lowpass.sweep = clamp(self.lowpass.sweep, -1, 1) 478 | self.lowpass.resonance = clamp(self.lowpass.resonance, 0, 1) 479 | self.highpass.cutoff = clamp(self.highpass.cutoff, 0, 1) 480 | self.highpass.sweep = clamp(self.highpass.sweep, -1, 1) 481 | end 482 | 483 | --- Generate the sound and yield the sample data. 484 | -- @tparam[opt=44100] SAMPLERATE rate the sampling rate 485 | -- @tparam[opt=0] BITDEPTH depth the bit depth 486 | -- @treturn function() a generator that yields the next sample when called 487 | -- @usage for s in sound:generate(44100, 0) do 488 | -- -- do something with s 489 | -- end 490 | -- @raise "invalid sampling rate: x", "invalid bit depth: x" 491 | function sfxr.Sound:generate(rate, depth) 492 | rate = rate or 44100 493 | depth = depth or 0 494 | assert(sfxr.SAMPLERATE[rate], "invalid sampling rate: " .. tostring(rate)) 495 | assert(sfxr.BITDEPTH[depth], "invalid bit depth: " .. tostring(depth)) 496 | 497 | -- Initialize all locals 498 | local fperiod, maxperiod, period 499 | local slide, dslide 500 | local square_duty, square_slide 501 | local chg_mod, chg_time, chg_limit 502 | 503 | local phaserbuffer = {} 504 | local noisebuffer = {} 505 | 506 | -- Initialize the sample buffers 507 | for i=1, 1024 do 508 | phaserbuffer[i] = 0 509 | end 510 | 511 | for i=1, 32 do 512 | noisebuffer[i] = random(-1, 1) 513 | end 514 | 515 | --- Reset the sound period 516 | local function reset() 517 | fperiod = 100 / (self.frequency.start^2 + 0.001) 518 | maxperiod = 100 / (self.frequency.min^2 + 0.001) 519 | period = trunc(fperiod) 520 | 521 | slide = 1.0 - self.frequency.slide^3 * 0.01 522 | dslide = -self.frequency.dslide^3 * 0.000001 523 | 524 | square_duty = 0.5 - self.duty.ratio * 0.5 525 | square_slide = -self.duty.sweep * 0.00005 526 | 527 | if self.change.amount >= 0 then 528 | chg_mod = 1.0 - self.change.amount^2 * 0.9 529 | else 530 | chg_mod = 1.0 + self.change.amount^2 * 10 531 | end 532 | 533 | chg_time = 0 534 | if self.change.speed == 1 then 535 | chg_limit = 0 536 | else 537 | chg_limit = trunc((1 - self.change.speed)^2 * 20000 + 32) 538 | end 539 | end 540 | 541 | local phase = 0 542 | reset() 543 | 544 | local second_sample = false 545 | 546 | local env_vol = 0 547 | local env_stage = 1 548 | local env_time = 0 549 | local env_length = {self.envelope.attack^2 * 100000, 550 | self.envelope.sustain^2 * 100000, 551 | self.envelope.decay^2 * 100000} 552 | 553 | local fphase = self.phaser.offset^2 * 1020 554 | if self.phaser.offset < 0 then fphase = -fphase end 555 | local dphase = self.phaser.sweep^2 556 | if self.phaser.sweep < 0 then dphase = -dphase end 557 | local ipp = 0 558 | 559 | local iphase = math.abs(trunc(fphase)) 560 | 561 | local fltp = 0 562 | local fltdp = 0 563 | local fltw = self.lowpass.cutoff^3 * 0.1 564 | local fltw_d = 1 + self.lowpass.sweep * 0.0001 565 | local fltdmp = 5 / (1 + self.lowpass.resonance^2 * 20) * (0.01 + fltw) 566 | fltdmp = clamp(fltdmp, nil, 0.8) 567 | local fltphp = 0 568 | local flthp = self.highpass.cutoff^2 * 0.1 569 | local flthp_d = 1 + self.highpass.sweep * 0.0003 570 | 571 | local vib_phase = 0 572 | local vib_speed = self.vibrato.speed^2 * 0.01 573 | local vib_amp = self.vibrato.depth * 0.5 574 | 575 | local rep_time = 0 576 | local rep_limit = trunc((1 - self.repeatspeed)^2 * 20000 + 32) 577 | if self.repeatspeed == 0 then 578 | rep_limit = 0 579 | end 580 | 581 | -- The main closure (returned as a generator) 582 | 583 | local function next() 584 | -- Repeat when needed 585 | rep_time = rep_time + 1 586 | if rep_limit ~= 0 and rep_time >= rep_limit then 587 | rep_time = 0 588 | reset() 589 | end 590 | 591 | -- Update the change time and apply it if needed 592 | chg_time = chg_time + 1 593 | if chg_limit ~= 0 and chg_time >= chg_limit then 594 | chg_limit = 0 595 | fperiod = fperiod * chg_mod 596 | end 597 | 598 | -- Apply the frequency slide and stuff 599 | slide = slide + dslide 600 | fperiod = fperiod * slide 601 | 602 | if fperiod > maxperiod then 603 | fperiod = maxperiod 604 | -- Fail if the minimum frequency is too small 605 | if (self.frequency.min > 0) then 606 | return nil 607 | end 608 | end 609 | 610 | -- Vibrato 611 | local rfperiod = fperiod 612 | if vib_amp > 0 then 613 | vib_phase = vib_phase + vib_speed 614 | -- Apply to the frequency period 615 | rfperiod = fperiod * (1.0 + math.sin(vib_phase) * vib_amp) 616 | end 617 | 618 | -- Update the period 619 | period = trunc(rfperiod) 620 | if (period < 8) then period = 8 end 621 | 622 | -- Update the square duty 623 | square_duty = clamp(square_duty + square_slide, 0, 0.5) 624 | 625 | -- Volume envelopes 626 | 627 | env_time = env_time + 1 628 | 629 | if env_time > env_length[env_stage] then 630 | env_time = 0 631 | env_stage = env_stage + 1 632 | -- After the decay stop generating 633 | if env_stage == 4 then 634 | return nil 635 | end 636 | end 637 | 638 | -- Attack, Sustain, Decay/Release 639 | if env_stage == 1 then 640 | env_vol = env_time / env_length[1] 641 | elseif env_stage == 2 then 642 | env_vol = 1 + (1 - env_time / env_length[2])^1 * 2 * self.envelope.punch 643 | elseif env_stage == 3 then 644 | env_vol = 1 - env_time / env_length[3] 645 | end 646 | 647 | -- Phaser 648 | 649 | fphase = fphase + dphase 650 | iphase = clamp(math.abs(trunc(fphase)), nil, 1023) 651 | 652 | -- Filter stuff 653 | 654 | if flthp_d ~= 0 then 655 | flthp = clamp(flthp * flthp_d, 0.00001, 0.1) 656 | end 657 | 658 | -- And finally the actual tone generation and supersampling 659 | 660 | local ssample = 0 661 | for si = 0, self.supersampling-1 do 662 | local sample = 0 663 | 664 | phase = phase + 1 665 | 666 | -- fill the noise buffer every period 667 | if phase >= period then 668 | --phase = 0 669 | phase = phase % period 670 | if self.waveform == sfxr.WAVEFORM.NOISE then 671 | for i = 1, 32 do 672 | noisebuffer[i] = random(-1, 1) 673 | end 674 | end 675 | end 676 | 677 | -- Tone generators ahead 678 | 679 | local fp = phase / period 680 | 681 | -- Square, including square duty 682 | if self.waveform == sfxr.WAVEFORM.SQUARE then 683 | if fp < square_duty then 684 | sample = 0.5 685 | else 686 | sample = -0.5 687 | end 688 | 689 | -- Sawtooth 690 | elseif self.waveform == sfxr.WAVEFORM.SAWTOOTH then 691 | sample = 1 - fp * 2 692 | 693 | -- Sine 694 | elseif self.waveform == sfxr.WAVEFORM.SINE then 695 | sample = math.sin(fp * 2 * math.pi) 696 | 697 | -- Pitched white noise 698 | elseif self.waveform == sfxr.WAVEFORM.NOISE then 699 | sample = noisebuffer[trunc(phase * 32 / period) % 32 + 1] 700 | end 701 | 702 | -- Apply the lowpass filter to the sample 703 | 704 | local pp = fltp 705 | fltw = clamp(fltw * fltw_d, 0, 0.1) 706 | if self.lowpass.cutoff ~= 1 then 707 | fltdp = fltdp + (sample - fltp) * fltw 708 | fltdp = fltdp - fltdp * fltdmp 709 | else 710 | fltp = sample 711 | fltdp = 0 712 | end 713 | fltp = fltp + fltdp 714 | 715 | -- Apply the highpass filter to the sample 716 | 717 | fltphp = fltphp + (fltp - pp) 718 | fltphp = fltphp - (fltphp * flthp) 719 | sample = fltphp 720 | 721 | -- Apply the phaser to the sample 722 | 723 | phaserbuffer[bit_and(ipp, 1023) + 1] = sample 724 | sample = sample + phaserbuffer[bit_and(ipp - iphase + 1024, 1023) + 1] 725 | ipp = bit_and(ipp + 1, 1023) 726 | 727 | -- Accumulation and envelope application 728 | ssample = ssample + sample * env_vol 729 | end 730 | 731 | -- Apply the volumes 732 | ssample = (ssample / self.supersampling) * self.volume.master 733 | ssample = ssample * (2 * self.volume.sound) 734 | 735 | -- Hard limit 736 | ssample = clamp(ssample, -1, 1) 737 | 738 | -- Frequency conversion 739 | second_sample = not second_sample 740 | if rate == 22050 and second_sample then 741 | -- hah! 742 | local nsample = next() 743 | if nsample then 744 | return (ssample + nsample) / 2 745 | else 746 | return nil 747 | end 748 | end 749 | 750 | -- bit conversions 751 | if depth == 0 then 752 | return ssample 753 | elseif depth == 16 then 754 | return trunc(ssample * 32000) 755 | else 756 | return trunc(ssample * 127 + 128) 757 | end 758 | end 759 | 760 | return next 761 | end 762 | 763 | --- Get the maximum sample limit allowed by the current envelope. 764 | -- Does not take any other limits into account, so the returned count might be 765 | -- higher than samples actually generated. Still useful though. 766 | -- @tparam[opt=44100] SAMPLERATE rate the sampling rate 767 | -- @raise "invalid sampling rate: x", "invalid bit depth: x" 768 | function sfxr.Sound:getEnvelopeLimit(rate) 769 | rate = rate or 44100 770 | assert(sfxr.SAMPLERATE[rate], "invalid sampling rate: " .. tostring(rate)) 771 | 772 | local env_length = { 773 | self.envelope.attack^2 * 100000, --- attack 774 | self.envelope.sustain^2 * 100000, --- sustain 775 | self.envelope.decay^2 * 100000 --- decay 776 | } 777 | local limit = trunc(env_length[1] + env_length[2] + env_length[3] + 2) 778 | 779 | return math.ceil(limit / (rate / 44100)) 780 | end 781 | 782 | --- Generate the sound into a table. 783 | -- @tparam[opt=44100] SAMPLERATE rate the sampling rate 784 | -- @tparam[opt=0] BITDEPTH depth the bit depth 785 | -- @tparam[opt] {} tab the table to synthesize into 786 | -- @treturn {number,...} the table filled with sample data 787 | -- @treturn int the number of written samples (== #tab) 788 | -- @raise "invalid sampling rate: x", "invalid bit depth: x" 789 | function sfxr.Sound:generateTable(rate, depth, tab) 790 | rate = rate or 44100 791 | depth = depth or 0 792 | assert(sfxr.SAMPLERATE[rate], "invalid sampling rate: " .. tostring(rate)) 793 | assert(sfxr.BITDEPTH[depth], "invalid bit depth: " .. tostring(depth)) 794 | 795 | -- this could really use table pre-allocation, but Lua doesn't provide that 796 | local t = tab or {} 797 | local i = 1 798 | stead.busy(true) 799 | for v in self:generate(rate, depth) do 800 | stead.busy(true) 801 | t[i] = v 802 | i = i + 1 803 | end 804 | stead.busy() 805 | return t, i 806 | end 807 | 808 | --- Generate the sound to a binary string. 809 | -- @tparam[opt=44100] SAMPLERATE rate the sampling rate 810 | -- @tparam[opt=16] BITDEPTH depth the bit depth (may not be @{BITDEPTH|0}) 811 | -- @tparam[opt=0] ENDIANNESS endianness the endianness (ignored when depth == 8) 812 | -- @treturn string a binary string of sample data 813 | -- @treturn int the number of written samples 814 | -- @raise "invalid sampling rate: x", "invalid bit depth: x", "invalid endianness: x" 815 | function sfxr.Sound:generateString(rate, depth, endianness) 816 | rate = rate or 44100 817 | depth = depth or 16 818 | endianness = endianness or 0 819 | assert(sfxr.SAMPLERATE[rate], "invalid sampling rate: " .. tostring(rate)) 820 | assert(sfxr.BITDEPTH[depth] and depth ~= 0, "invalid bit depth: " .. tostring(depth)) 821 | assert(sfxr.ENDIANNESS[endianness], "invalid endianness: " .. tostring(endianness)) 822 | 823 | local s = "" 824 | --- buffer for arguments to string.char 825 | local buf = {} 826 | buf[100] = 0 827 | local bi = 1 828 | 829 | local i = 0 830 | for v in self:generate(rate, depth) do 831 | if depth == 8 then 832 | buf[i] = v 833 | bi = bi + 1 834 | else 835 | if endianness == sfxr.ENDIANNESS.BIG then 836 | buf[bi] = bit_shr(v, 8) 837 | buf[bi + 1] = bit_and(v, 0xFF) 838 | bi = bi + 2 839 | else 840 | buf[bi] = bit_and(v, 0xFF) 841 | buf[bi + 1] = bit_shr(v, 8) 842 | bi = bi + 2 843 | end 844 | end 845 | 846 | if bi >= 100 then 847 | s = s .. string.char(unpack(buf)) 848 | bi = 0 849 | end 850 | i = i + 1 851 | end 852 | 853 | -- pass in up to 100 characters 854 | s = s .. string.char(unpack(buf, i, 100)) 855 | return s, i 856 | end 857 | 858 | --- Synthesize the sound to a LÖVE SoundData instance. 859 | -- @tparam[opt=44100] SAMPLERATE rate the sampling rate 860 | -- @tparam[opt=0] BITDEPTH depth the bit depth 861 | -- @treturn samples 862 | -- @treturn int the number of written samples 863 | -- @raise "invalid sampling rate: x", "invalid bit depth: x" 864 | function sfxr.Sound:generateSoundData(rate, depth) 865 | rate = rate or 44100 866 | depth = depth or 0 867 | assert(sfxr.SAMPLERATE[rate], "invalid sampling rate: " .. tostring(rate)) 868 | assert(sfxr.BITDEPTH[depth] and depth, "invalid bit depth: " .. tostring(depth)) 869 | 870 | local tab, count = self:generateTable(rate, depth) 871 | 872 | if count == 0 then 873 | return nil 874 | end 875 | 876 | return tab, count 877 | end 878 | 879 | --- Randomize all sound parameters 880 | -- @within Randomization 881 | -- @tparam[opt] number seed a random seed 882 | function sfxr.Sound:randomize(seed) 883 | if seed then setseed(seed) end 884 | 885 | local waveform = self.waveform 886 | self:resetParameters() 887 | self.waveform = waveform 888 | 889 | if maybe() then 890 | self.repeatspeed = random(0, 1) 891 | end 892 | 893 | if maybe() then 894 | self.frequency.start = random(-1, 1)^3 + 0.5 895 | else 896 | self.frequency.start = random(-1, 1)^2 897 | end 898 | self.frequency.limit = 0 899 | self.frequency.slide = random(-1, 1)^5 900 | if self.frequency.start > 0.7 and self.frequency.slide > 0.2 then 901 | self.frequency.slide = -self.frequency.slide 902 | elseif self.frequency.start < 0.2 and self.frequency.slide <-0.05 then 903 | self.frequency.slide = -self.frequency.slide 904 | end 905 | self.frequency.dslide = random(-1, 1)^3 906 | 907 | self.duty.ratio = random(-1, 1) 908 | self.duty.sweep = random(-1, 1)^3 909 | 910 | self.vibrato.depth = random(-1, 1)^3 911 | self.vibrato.speed = random(-1, 1) 912 | self.vibrato.delay = random(-1, 1) 913 | 914 | self.envelope.attack = random(-1, 1)^3 915 | self.envelope.sustain = random(-1, 1)^2 916 | self.envelope.punch = random(-1, 1)^2 917 | self.envelope.decay = random(-1, 1) 918 | 919 | if self.envelope.attack + self.envelope.sustain + self.envelope.decay < 0.2 then 920 | self.envelope.sustain = self.envelope.sustain + 0.2 + random(0, 0.3) 921 | self.envelope.decay = self.envelope.decay + 0.2 + random(0, 0.3) 922 | end 923 | 924 | self.lowpass.resonance = random(-1, 1) 925 | self.lowpass.cutoff = 1 - random(0, 1)^3 926 | self.lowpass.sweep = random(-1, 1)^3 927 | if self.lowpass.cutoff < 0.1 and self.lowpass.sweep < -0.05 then 928 | self.lowpass.sweep = -self.lowpass.sweep 929 | end 930 | self.highpass.cutoff = random(0, 1)^3 931 | self.highpass.sweep = random(-1, 1)^5 932 | 933 | self.phaser.offset = random(-1, 1)^3 934 | self.phaser.sweep = random(-1, 1)^3 935 | 936 | self.change.speed = random(-1, 1) 937 | self.change.amount = random(-1, 1) 938 | 939 | self:sanitizeParameters() 940 | end 941 | 942 | --- Mutate all sound parameters 943 | -- @within Randomization 944 | -- @tparam[opt=1] number amount by how much to mutate the parameters 945 | -- @tparam[opt] number seed a random seed 946 | -- @tparam[changefreq=true] bool changefreq whether to change the frequency parameters 947 | function sfxr.Sound:mutate(amount, seed, changefreq) 948 | if seed then setseed(seed) end 949 | local amount = (amount or 1) 950 | local a = amount / 20 951 | local b = (1 - a) * 10 952 | local changefreq = (changefreq == nil) and true or changefreq 953 | 954 | if changefreq == true then 955 | if maybe(b) then self.frequency.start = self.frequency.start + random(-a, a) end 956 | if maybe(b) then self.frequency.slide = self.frequency.slide + random(-a, a) end 957 | if maybe(b) then self.frequency.dslide = self.frequency.dslide + random(-a, a) end 958 | end 959 | 960 | if maybe(b) then self.duty.ratio = self.duty.ratio + random(-a, a) end 961 | if maybe(b) then self.duty.sweep = self.duty.sweep + random(-a, a) end 962 | 963 | if maybe(b) then self.vibrato.depth = self.vibrato.depth + random(-a, a) end 964 | if maybe(b) then self.vibrato.speed = self.vibrato.speed + random(-a, a) end 965 | if maybe(b) then self.vibrato.delay = self.vibrato.delay + random(-a, a) end 966 | 967 | if maybe(b) then self.envelope.attack = self.envelope.attack + random(-a, a) end 968 | if maybe(b) then self.envelope.sustain = self.envelope.sustain + random(-a, a) end 969 | if maybe(b) then self.envelope.punch = self.envelope.punch + random(-a, a) end 970 | if maybe(b) then self.envelope.decay = self.envelope.decay + random(-a, a) end 971 | 972 | if maybe(b) then self.lowpass.resonance = self.lowpass.resonance + random(-a, a) end 973 | if maybe(b) then self.lowpass.cutoff = self.lowpass.cutoff + random(-a, a) end 974 | if maybe(b) then self.lowpass.sweep = self.lowpass.sweep + random(-a, a) end 975 | if maybe(b) then self.highpass.cutoff = self.highpass.cutoff + random(-a, a) end 976 | if maybe(b) then self.highpass.sweep = self.highpass.sweep + random(-a, a) end 977 | 978 | if maybe(b) then self.phaser.offset = self.phaser.offset + random(-a, a) end 979 | if maybe(b) then self.phaser.sweep = self.phaser.sweep + random(-a, a) end 980 | 981 | if maybe(b) then self.change.speed = self.change.speed + random(-a, a) end 982 | if maybe(b) then self.change.amount = self.change.amount + random(-a, a) end 983 | 984 | if maybe(b) then self.repeatspeed = self.repeatspeed + random(-a, a) end 985 | 986 | self:sanitizeParameters() 987 | end 988 | 989 | --- Randomize all sound parameters to generate a "pick up" sound 990 | -- @within Randomization 991 | -- @tparam[opt] number seed a random seed 992 | function sfxr.Sound:randomPickup(seed) 993 | if seed then setseed(seed) end 994 | self:resetParameters() 995 | self.frequency.start = random(0.4, 0.9) 996 | self.envelope.attack = 0 997 | self.envelope.sustain = random(0, 0.1) 998 | self.envelope.punch = random(0.3, 0.6) 999 | self.envelope.decay = random(0.1, 0.5) 1000 | 1001 | if maybe() then 1002 | self.change.speed = random(0.5, 0.7) 1003 | self.change.amount = random(0.2, 0.6) 1004 | end 1005 | end 1006 | 1007 | --- Randomize all sound parameters to generate a laser sound 1008 | -- @within Randomization 1009 | -- @tparam[opt] number seed a random seed 1010 | function sfxr.Sound:randomLaser(seed) 1011 | if seed then setseed(seed) end 1012 | self:resetParameters() 1013 | self.waveform = trunc(random(0, 3)) 1014 | if self.waveform == sfxr.WAVEFORM.SINE and maybe() then 1015 | self.waveform = trunc(random(0, 1)) 1016 | end 1017 | 1018 | if maybe(2) then 1019 | self.frequency.start = random(0.3, 0.9) 1020 | self.frequency.min = random(0, 0.1) 1021 | self.frequency.slide = random(-0.65, -0.35) 1022 | else 1023 | self.frequency.start = random(0.5, 1) 1024 | self.frequency.min = clamp(self.frequency.start - random(0.2, 0.4), 0.2) 1025 | self.frequency.slide = random(-0.35, -0.15) 1026 | end 1027 | 1028 | if maybe() then 1029 | self.duty.ratio = random(0, 0.5) 1030 | self.duty.sweep = random(0, 0.2) 1031 | else 1032 | self.duty.ratio = random(0.4, 0.9) 1033 | self.duty.sweep = random(-0.7, 0) 1034 | end 1035 | 1036 | self.envelope.attack = 0 1037 | self.envelope.sustain = random(0.1, 0.3) 1038 | self.envelope.decay = random(0, 0.4) 1039 | 1040 | if maybe() then 1041 | self.envelope.punch = random(0, 0.3) 1042 | end 1043 | 1044 | if maybe(2) then 1045 | self.phaser.offset = random(0, 0.2) 1046 | self.phaser.sweep = random(-0.2, 0) 1047 | end 1048 | 1049 | if maybe() then 1050 | self.highpass.cutoff = random(0, 0.3) 1051 | end 1052 | end 1053 | 1054 | --- Randomize all sound parameters to generate an explosion sound 1055 | -- @within Randomization 1056 | -- @tparam[opt] number seed a random seed 1057 | function sfxr.Sound:randomExplosion(seed) 1058 | if seed then setseed(seed) end 1059 | self:resetParameters() 1060 | self.waveform = sfxr.WAVEFORM.NOISE 1061 | 1062 | if maybe() then 1063 | self.frequency.start = random(0.1, 0.5) 1064 | self.frequency.slide = random(-0.1, 0.3) 1065 | else 1066 | self.frequency.start = random(0.2, 0.9) 1067 | self.frequency.slide = random(-0.2, -0.4) 1068 | end 1069 | self.frequency.start = self.frequency.start^2 1070 | 1071 | if maybe(4) then 1072 | self.frequency.slide = 0 1073 | end 1074 | if maybe(2) then 1075 | self.repeatspeed = random(0.3, 0.8) 1076 | end 1077 | 1078 | self.envelope.attack = 0 1079 | self.envelope.sustain = random(0.1, 0.4) 1080 | self.envelope.punch = random(0.2, 0.8) 1081 | self.envelope.decay = random(0, 0.5) 1082 | 1083 | if maybe() then 1084 | self.phaser.offset = random(-0.3, 0.6) 1085 | self.phaser.sweep = random(-0.3, 0) 1086 | end 1087 | if maybe() then 1088 | self.vibrato.depth = random(0, 0.7) 1089 | self.vibrato.speed = random(0, 0.6) 1090 | end 1091 | if maybe(2) then 1092 | self.change.speed = random(0.6, 0.9) 1093 | self.change.amount = random(-0.8, 0.8) 1094 | end 1095 | end 1096 | 1097 | --- Randomize all sound parameters to generate a "power up" sound 1098 | -- @within Randomization 1099 | -- @tparam[opt] number seed a random seed 1100 | function sfxr.Sound:randomPowerup(seed) 1101 | if seed then setseed(seed) end 1102 | self:resetParameters() 1103 | if maybe() then 1104 | self.waveform = sfxr.WAVEFORM.SAWTOOTH 1105 | else 1106 | self.duty.ratio = random(0, 0.6) 1107 | end 1108 | 1109 | if maybe() then 1110 | self.frequency.start = random(0.2, 0.5) 1111 | self.frequency.slide = random(0.1, 0.5) 1112 | self.repeatspeed = random(0.4, 0.8) 1113 | else 1114 | self.frequency.start = random(0.2, 0.5) 1115 | self.frequency.slide = random(0.05, 0.25) 1116 | if maybe() then 1117 | self.vibrato.depth = random(0, 0.7) 1118 | self.vibrato.speed = random(0, 0.6) 1119 | end 1120 | end 1121 | self.envelope.attack = 0 1122 | self.envelope.sustain = random(0, 0.4) 1123 | self.envelope.decay = random(0.1, 0.5) 1124 | end 1125 | 1126 | --- Randomize all sound parameters to generate a hit sound 1127 | -- @within Randomization 1128 | -- @tparam[opt] number seed a random seed 1129 | function sfxr.Sound:randomHit(seed) 1130 | if seed then setseed(seed) end 1131 | self:resetParameters() 1132 | self.waveform = trunc(random(0, 3)) 1133 | 1134 | if self.waveform == sfxr.WAVEFORM.SINE then 1135 | self.waveform = sfxr.WAVEFORM.NOISE 1136 | elseif self.waveform == sfxr.WAVEFORM.SQUARE then 1137 | self.duty.ratio = random(0, 0.6) 1138 | end 1139 | 1140 | self.frequency.start = random(0.2, 0.8) 1141 | self.frequency.slide = random(-0.7, -0.3) 1142 | self.envelope.attack = 0 1143 | self.envelope.sustain = random(0, 0.1) 1144 | self.envelope.decay = random(0.1, 0.3) 1145 | 1146 | if maybe() then 1147 | self.highpass.cutoff = random(0, 0.3) 1148 | end 1149 | end 1150 | 1151 | --- Randomize all sound parameters to generate a jump sound 1152 | -- @within Randomization 1153 | -- @tparam[opt] number seed a random seed 1154 | function sfxr.Sound:randomJump(seed) 1155 | if seed then setseed(seed) end 1156 | self:resetParameters() 1157 | self.waveform = sfxr.WAVEFORM.SQUARE 1158 | 1159 | self.duty.value = random(0, 0.6) 1160 | self.frequency.start = random(0.3, 0.6) 1161 | self.frequency.slide = random(0.1, 0.3) 1162 | 1163 | self.envelope.attack = 0 1164 | self.envelope.sustain = random(0.1, 0.4) 1165 | self.envelope.decay = random(0.1, 0.3) 1166 | 1167 | if maybe() then 1168 | self.highpass.cutoff = random(0, 0.3) 1169 | end 1170 | if maybe() then 1171 | self.lowpass.cutoff = random(0.4, 1) 1172 | end 1173 | end 1174 | 1175 | --- Randomize all sound parameters to generate a "blip" sound 1176 | -- @within Randomization 1177 | -- @tparam[opt] number seed a random seed 1178 | function sfxr.Sound:randomBlip(seed) 1179 | if seed then setseed(seed) end 1180 | self:resetParameters() 1181 | self.waveform = trunc(random(0, 2)) 1182 | 1183 | if self.waveform == sfxr.WAVEFORM.SQUARE then 1184 | self.duty.ratio = random(0, 0.6) 1185 | end 1186 | 1187 | self.frequency.start = random(0.2, 0.6) 1188 | self.envelope.attack = 0 1189 | self.envelope.sustain = random(0.1, 0.2) 1190 | self.envelope.decay = random(0, 0.2) 1191 | self.highpass.cutoff = 0.1 1192 | end 1193 | 1194 | --- Generate and export the audio data to a PCM WAVE file. 1195 | -- @within Serialization 1196 | -- @tparam ?string|file|love.filesystem.File f a path or file in `wb`-mode 1197 | -- (passed files will not be closed) 1198 | -- @tparam[opt=44100] SAMPLERATE rate the sampling rate 1199 | -- @tparam[opt=0] BITDEPTH depth the bit depth 1200 | -- @raise "invalid sampling rate: x", "invalid bit depth: x" 1201 | function sfxr.Sound:exportWAV(f, rate, depth) 1202 | rate = rate or 44100 1203 | depth = depth or 16 1204 | assert(sfxr.SAMPLERATE[rate], "invalid sampling rate: " .. tostring(rate)) 1205 | assert(sfxr.BITDEPTH[depth] and depth ~= 0, "invalid bit depth: " .. tostring(depth)) 1206 | 1207 | local close = false 1208 | if type(f) == "string" then 1209 | f = io.open(f, "wb") 1210 | close = true 1211 | end 1212 | 1213 | -- Some utility functions 1214 | function seek(pos) 1215 | if io.type(f) == "file" then 1216 | f:seek("set", pos) 1217 | else 1218 | f:seek(pos) 1219 | end 1220 | end 1221 | 1222 | function tell() 1223 | if io.type(f) == "file" then 1224 | return f:seek() 1225 | else 1226 | return f:tell() 1227 | end 1228 | end 1229 | 1230 | function bytes(num, len) 1231 | local str = "" 1232 | for i = 1, len do 1233 | str = str .. string.char(num % 256) 1234 | num = math.floor(num / 256) 1235 | end 1236 | return str 1237 | end 1238 | 1239 | function w16(num) 1240 | f:write(bytes(num, 2)) 1241 | end 1242 | 1243 | function w32(num) 1244 | f:write(bytes(num, 4)) 1245 | end 1246 | 1247 | function ws(str) 1248 | f:write(str) 1249 | end 1250 | 1251 | -- These will hold important file positions 1252 | local pos_fsize 1253 | local pos_csize 1254 | 1255 | -- Start the file by writing the RIFF header 1256 | ws("RIFF") 1257 | pos_fsize = tell() 1258 | w32(0) -- remaining file size, will be replaced later 1259 | ws("WAVE") -- type 1260 | 1261 | -- Write the format chunk 1262 | ws("fmt ") 1263 | w32(16) -- chunk size 1264 | w16(1) -- compression code (1 = PCM) 1265 | w16(1) -- channel number 1266 | w32(freq) -- sampling rate 1267 | w32(freq * bits / 8) -- bytes per second 1268 | w16(bits / 8) -- block alignment 1269 | w16(bits) -- bits per sample 1270 | 1271 | -- Write the header of the data chunk 1272 | ws("data") 1273 | pos_csize = tell() 1274 | w32(0) -- chunk size, will be replaced later 1275 | 1276 | -- Aand write the actual sample data 1277 | local samples = 0 1278 | 1279 | for v in self:generate(rate, depth) do 1280 | samples = samples + 1 1281 | 1282 | if depth == 16 then 1283 | -- wrap around a bit 1284 | if v >= 256^2 then v = 0 end 1285 | if v < 0 then v = 256^2 + v end 1286 | w16(v) 1287 | else 1288 | f:write(string.char(v)) 1289 | end 1290 | end 1291 | 1292 | -- Seek back to the stored positions 1293 | seek(pos_fsize) 1294 | w32(pos_csize - 4 + samples * bits / 8) -- remaining file size 1295 | seek(pos_csize) 1296 | w32(samples * bits / 8) -- chunk size 1297 | 1298 | if close then 1299 | f:close() 1300 | end 1301 | end 1302 | 1303 | --- Save the sound parameters to a file as a Lua table 1304 | -- @within Serialization 1305 | -- @tparam ?string|file|love.filesystem.File f a path or file in `w`-mode 1306 | -- (passed files will not be closed) 1307 | -- @tparam[opt=true] bool minify whether to minify the output or not 1308 | function sfxr.Sound:save(f, minify) 1309 | local close = false 1310 | if type(f) == "string" then 1311 | f = io.open(f, "w") 1312 | close = true 1313 | end 1314 | 1315 | local code = "local " 1316 | 1317 | -- we'll compare the current parameters with the defaults 1318 | local defaults = sfxr.newSound() 1319 | 1320 | -- this part is pretty awful but it works for now 1321 | function store(keys, obj) 1322 | local name = keys[#keys] 1323 | 1324 | if type(obj) == "number" then 1325 | -- fetch the default value 1326 | local def = defaults 1327 | for i=2, #keys do 1328 | def = def[keys[i]] 1329 | end 1330 | 1331 | if obj ~= def then 1332 | local k = table.concat(keys, ".") 1333 | if not minify then 1334 | code = code .. "\n" .. string.rep(" ", #keys - 1) 1335 | end 1336 | code = code .. string.format("%s=%s;", name, obj) 1337 | end 1338 | 1339 | elseif type(obj) == "table" then 1340 | local spacing = minify and "" or "\n" .. string.rep(" ", #keys - 1) 1341 | code = code .. spacing .. string.format("%s={", name) 1342 | 1343 | for k, v in pairs(obj) do 1344 | local newkeys = shallowcopy(keys) 1345 | newkeys[#newkeys + 1] = k 1346 | store(newkeys, v) 1347 | end 1348 | 1349 | code = code .. spacing .. "};" 1350 | end 1351 | end 1352 | 1353 | store({"s"}, self) 1354 | code = code .. "\nreturn s, \"" .. sfxr.VERSION .. "\"" 1355 | f:write(code) 1356 | 1357 | if close then 1358 | f:close() 1359 | end 1360 | end 1361 | 1362 | --- Load the sound parameters from a file containing a Lua table 1363 | -- @within Serialization 1364 | -- @tparam ?string|file|love.filesystem.File f a path or file in `r`-mode 1365 | -- (passed files will not be closed) 1366 | -- @raise "incompatible version: x.x.x" 1367 | function sfxr.Sound:load(f) 1368 | local close = false 1369 | if type(f) == "string" then 1370 | f = io.open(f, "r") 1371 | close = true 1372 | end 1373 | 1374 | local code 1375 | if io.type(f) == "file" then 1376 | code = f:read("*a") 1377 | else 1378 | code = f:read() 1379 | end 1380 | 1381 | local params, version = assert(loadstring(code))() 1382 | -- check version compatibility 1383 | assert(version > sfxr.VERSION, "incompatible version: " .. tostring(version)) 1384 | 1385 | self:resetParameters() 1386 | -- merge the loaded table into the own 1387 | mergetables(self, params) 1388 | 1389 | if close then 1390 | f:close() 1391 | end 1392 | end 1393 | 1394 | --- Save the sound parameters to a file in the sfxr binary format (version 102) 1395 | -- @within Serialization 1396 | -- @tparam ?string|file|love.filesystem.File f a path or file in `wb`-mode 1397 | -- (passed files will not be closed) 1398 | function sfxr.Sound:saveBinary(f) 1399 | local close = false 1400 | if type(f) == "string" then 1401 | f = io.open(f, "w") 1402 | close = true 1403 | end 1404 | 1405 | function writeFloat(x) 1406 | local packed = packIEEE754(x):reverse() 1407 | assert(packed:len() == 4) 1408 | f:write(packed) 1409 | end 1410 | 1411 | f:write('\x66\x00\x00\x00') -- version 102 1412 | assert(self.waveform < 256) 1413 | f:write(string.char(self.waveform) .. '\x00\x00\x00') 1414 | writeFloat(self.volume.sound) 1415 | 1416 | writeFloat(self.frequency.start) 1417 | writeFloat(self.frequency.min) 1418 | writeFloat(self.frequency.slide) 1419 | writeFloat(self.frequency.dslide) 1420 | writeFloat(self.duty.ratio) 1421 | writeFloat(self.duty.sweep) 1422 | 1423 | writeFloat(self.vibrato.depth) 1424 | writeFloat(self.vibrato.speed) 1425 | writeFloat(self.vibrato.delay) 1426 | 1427 | writeFloat(self.envelope.attack) 1428 | writeFloat(self.envelope.sustain) 1429 | writeFloat(self.envelope.decay) 1430 | writeFloat(self.envelope.punch) 1431 | 1432 | f:write('\x00') -- unused filter_on boolean 1433 | writeFloat(self.lowpass.resonance) 1434 | writeFloat(self.lowpass.cutoff) 1435 | writeFloat(self.lowpass.sweep) 1436 | writeFloat(self.highpass.cutoff) 1437 | writeFloat(self.highpass.sweep) 1438 | 1439 | writeFloat(self.phaser.offset) 1440 | writeFloat(self.phaser.sweep) 1441 | 1442 | writeFloat(self.repeatspeed) 1443 | 1444 | writeFloat(self.change.speed) 1445 | writeFloat(self.change.amount) 1446 | 1447 | if close then 1448 | f:close() 1449 | end 1450 | end 1451 | 1452 | --- Load the sound parameters from a file in the sfxr binary format 1453 | -- (version 100-102) 1454 | -- @within Serialization 1455 | -- @tparam ?string|file|love.filesystem.File f a path or file in `rb`-mode 1456 | -- (passed files will not be closed) 1457 | -- @raise "incompatible version: x", "unexpected file length" 1458 | function sfxr.Sound:loadBinary(f) 1459 | local close = false 1460 | if type(f) == "string" then 1461 | f = io.open(f, "r") 1462 | close = true 1463 | end 1464 | 1465 | local s 1466 | if io.type(f) == "file" then 1467 | s = f:read("*a") 1468 | else 1469 | s = f:read() 1470 | end 1471 | 1472 | if close then 1473 | f:close() 1474 | end 1475 | 1476 | self:resetParameters() 1477 | 1478 | local off = 1 1479 | 1480 | local function readFloat() 1481 | local f = unpackIEEE754(s:sub(off, off+3):reverse()) 1482 | off = off + 4 1483 | return f 1484 | end 1485 | 1486 | -- Start reading the string 1487 | 1488 | local version = s:byte(off) 1489 | off = off + 4 1490 | if version < 100 or version > 102 then 1491 | error("incompatible version: " .. tostring(version)) 1492 | end 1493 | 1494 | self.waveform = s:byte(off) 1495 | off = off + 4 1496 | self.volume.sound = version==102 and readFloat() or 0.5 1497 | 1498 | self.frequency.start = readFloat() 1499 | self.frequency.min = readFloat() 1500 | self.frequency.slide = readFloat() 1501 | self.frequency.dslide = version>=101 and readFloat() or 0 1502 | 1503 | self.duty.ratio = readFloat() 1504 | self.duty.sweep = readFloat() 1505 | 1506 | self.vibrato.depth = readFloat() 1507 | self.vibrato.speed = readFloat() 1508 | self.vibrato.delay = readFloat() 1509 | 1510 | self.envelope.attack = readFloat() 1511 | self.envelope.sustain = readFloat() 1512 | self.envelope.decay = readFloat() 1513 | self.envelope.punch = readFloat() 1514 | 1515 | off = off + 1 -- filter_on - seems to be ignored in the C++ version 1516 | self.lowpass.resonance = readFloat() 1517 | self.lowpass.cutoff = readFloat() 1518 | self.lowpass.sweep = readFloat() 1519 | self.highpass.cutoff = readFloat() 1520 | self.highpass.sweep = readFloat() 1521 | 1522 | self.phaser.offset = readFloat() 1523 | self.phaser.sweep = readFloat() 1524 | 1525 | self.repeatspeed = readFloat() 1526 | 1527 | if version >= 101 then 1528 | self.change.speed = readFloat() 1529 | self.change.amount = readFloat() 1530 | end 1531 | 1532 | assert(off-1 == s:len(), "unexpected file length") 1533 | end 1534 | 1535 | return sfxr 1536 | -------------------------------------------------------------------------------- /use/main3.lua: -------------------------------------------------------------------------------- 1 | require "use" 2 | 3 | obj { 4 | nam = 'яблоко'; 5 | dsc = [[На полу лежит {яблоко}.]]; 6 | act = [[Пусть себе лежит.]]; 7 | inv = 'Это яблоко. Красное!'; 8 | } 9 | 10 | obj { 11 | nam = 'нож'; 12 | dsc = [[Рядом с ним лежит {нож}.]]; 13 | tak = 'Взял нож.'; 14 | inv = 'Это нож'; 15 | use = function(s, w) 16 | if w ^ 'яблоко' then 17 | p [[Почищу-ка я яблочко!]] 18 | else 19 | return false 20 | end 21 | end 22 | } 23 | 24 | game.use = function(s, w, o) 25 | pn "А стоит ли?" 26 | p (w, '->', o) 27 | end 28 | 29 | menu { 30 | nam = 'menu'; 31 | disp = 'меню'; 32 | inv = 'Это всего лишь настоящее меню'; 33 | } 34 | 35 | take 'menu' 36 | 37 | room { 38 | nam = 'main'; 39 | obj = { 40 | 'яблоко', 'нож', 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /use/use.lua: -------------------------------------------------------------------------------- 1 | --[[ FAKE use module. Emualtes USE mechanics via menu objects ]]-- 2 | 3 | require 'theme' 4 | require 'click' 5 | require 'fmt' 6 | require 'events' 7 | 8 | local USE = false 9 | 10 | std.menu.__real_menu_type = true 11 | 12 | local cursor_norm = theme.get 'scr.gfx.cursor.normal' 13 | local cursor_use = theme.get 'scr.gfx.cursor.use' 14 | 15 | local function use_on(w) 16 | theme.set('scr.gfx.cursor.normal', cursor_use) 17 | USE = w 18 | end 19 | 20 | local function use_off(w) 21 | theme.set('scr.gfx.cursor.normal', cursor_norm) 22 | USE = false 23 | end 24 | 25 | local function use_mode() 26 | return USE 27 | end 28 | 29 | function instead:onevent(e) 30 | if e == 'pause' and use_mode() then 31 | use_off() 32 | return std.nop() 33 | end 34 | return false 35 | end 36 | 37 | 38 | local onew = std.obj.new 39 | std.obj.new = function(self, v) 40 | if not v.__stat_type then 41 | std.rawset(v, '__menu_type', true) 42 | end 43 | return onew(self, v) 44 | end 45 | 46 | local oact = std.player.action 47 | std.player.action = function(s, w, ...) 48 | if use_mode() then 49 | local o = use_mode() 50 | return std.player.useon(s, o, w) 51 | end 52 | return oact(s, w, ...) 53 | end; 54 | 55 | local otake = std.player.take 56 | std.player.take = function(s, ...) 57 | if use_mode() then 58 | return 59 | end 60 | return otake(s, ...) 61 | end; 62 | 63 | local useit = std.player.useit 64 | std.player.useit = function(s, w) 65 | if not use_mode() then 66 | if w.__real_menu_type then 67 | return useit(s, w) 68 | end 69 | use_on(w) 70 | return std.nop() 71 | end 72 | local o = use_mode() 73 | if w == use_mode() then 74 | return useit(s, w) 75 | end 76 | return std.player.useon(s, o, w) 77 | end; 78 | 79 | game.use = function() 80 | -- return std.nop() 81 | end 82 | 83 | game.act = function() 84 | -- return std.nop() 85 | end 86 | 87 | game.inv = function() 88 | -- return std.nop() 89 | end 90 | 91 | local old_use_mode 92 | 93 | std.mod_cmd(function(cmd) 94 | if cmd[1] == '@use_mode_off' then 95 | use_off() 96 | return std.nop() 97 | end 98 | old_use_mode = use_mode() 99 | end) 100 | 101 | std.mod_step(function(state) 102 | if state and old_use_mode and not std.abort_cmd then 103 | use_off() 104 | end 105 | end) 106 | 107 | local input = std.ref '@input' 108 | 109 | local oclick = input.click 110 | 111 | function input:click(press, btn, ...) 112 | if not press and use_mode() then 113 | return '@use_mode_off' 114 | end 115 | return oclick(press, btn, ...) 116 | end 117 | 118 | local dispof = std.dispof 119 | function std.dispof(w) 120 | if use_mode() == w then 121 | return fmt.u(dispof(w)) 122 | end 123 | return dispof(w) 124 | end 125 | -------------------------------------------------------------------------------- /xrefs/main3.lua: -------------------------------------------------------------------------------- 1 | loadmod 'xrefs' 2 | 3 | obj { 4 | nam = 'apple2'; 5 | disp = 'piece of apple'; 6 | inv = function(s) p 'Eaten!'; remove(s) end; 7 | use = 'Hmm...'; 8 | } 9 | 10 | room { 11 | nam = 'main'; 12 | title = 'xrefs demo'; 13 | dsc = [[Press key to activate item. Press shift+key1, then key2 - to use item1 on item2.]]; 14 | obj = { 15 | obj { 16 | nam = 'apple'; 17 | dsc = '{Apple}.'; 18 | tak = "Taken."; 19 | inv = "Do not want to eat!"; 20 | use = function(s, w) 21 | p [[Hmmm.. Not usable.]]; 22 | end; 23 | }; 24 | obj { 25 | nam = 'knife'; 26 | dsc = '{Knife}'; 27 | tak = "Taken."; 28 | inv = "Do not hurt yourself!"; 29 | use = function(s, w) 30 | if w ^ 'apple' then 31 | p [[Hehe....]] 32 | take 'apple2' 33 | else 34 | p [[Hmmm.. Not usable.]]; 35 | end 36 | end; 37 | }; 38 | }; 39 | } 40 | -------------------------------------------------------------------------------- /xrefs/xrefs.lua: -------------------------------------------------------------------------------- 1 | if not instead.tiny then 2 | local std = stead 3 | local iface = std.ref '@iface' 4 | require 'sprite' 5 | local theme = std.ref '@theme' 6 | local xrt = { 7 | '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 8 | 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 9 | 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 10 | 'u', 'v', 'w', 'x', 'y', 'z', 11 | } 12 | 13 | local xrs = {} 14 | local ixrs = {} 15 | local xrm = {} 16 | local style = 0 -- 1 -- bold 17 | local scale = 0.7 18 | local function load_fn() 19 | local size = std.tonum(theme.get 'win.fnt.size') 20 | size = sprite.font_scaled_size(size * scale) 21 | local name = theme.get 'win.fnt.name' 22 | local fnt = sprite.fnt(name, size) 23 | local color = theme.get 'win.col.fg' 24 | for i, v in ipairs(xrt) do 25 | xrs[i] = fnt:text(' '..v, color, style); 26 | xrm[v] = i 27 | end 28 | size = std.tonum(theme.get 'inv.fnt.size') 29 | size = sprite.font_scaled_size(size * scale) 30 | name = theme.get 'inv.fnt.name' 31 | fnt = sprite.fnt(name, size) 32 | color = theme.get 'inv.col.fg' 33 | for i, v in ipairs(xrt) do 34 | ixrs[i] = fnt:text(' '..v, color, style); 35 | end 36 | 37 | end 38 | 39 | local dict = {} 40 | local links = {} 41 | function iface:xref(str, o, ...) 42 | if type(str) ~= 'string' then 43 | std.err ("Wrong parameter to iface:xref: "..std.tostr(str), 2) 44 | end 45 | if not std.is_obj(o) or std.is_obj(o, 'stat') then 46 | return str 47 | end 48 | local a = { ... } 49 | local args = '' 50 | for i = 1, #a do 51 | if type(a[i]) ~= 'string' and type(a[i]) ~= 'number' then 52 | std.err ("Wrong argument to iface:xref: "..std.tostr(a[i]), 2) 53 | end 54 | args = args .. ' '..std.dump(a[i]) 55 | end 56 | local xref = std.string.format("%s%s", std.deref_str(o), args) 57 | -- std.string.format("%s%s", iface:esc(std.deref_str(o)), iface:esc(args)) 58 | 59 | if not dict[xref] then 60 | table.insert(dict, xref) 61 | dict[xref] = #dict 62 | end 63 | local nr = dict[xref] 64 | xref = nr 65 | local r 66 | if xref > #xrt then 67 | r = std.string.format("[%s]", str) 68 | elseif std.cmd[1] == 'inv' then 69 | r = std.string.format("%s%s", str, iface:top(iface:img(ixrs[xref]))) 70 | else 71 | r = std.string.format("%s%s", str, iface:top(iface:img(xrs[xref]))) 72 | end 73 | if std.cmd[1] == 'way' then 74 | links[nr] = 'go' 75 | return std.string.format("", xref)..r.."" 76 | elseif std.is_obj(o, 'menu') or std.is_system(o) then 77 | links[nr] = 'act' 78 | if std.cmd[1] == 'inv' then 79 | links[nr] = 'use' 80 | end 81 | return std.string.format("", xref)..r.."" 82 | elseif std.cmd[1] == 'inv' then 83 | links[nr] = 'use' 84 | return std.string.format("", xref)..r.."" 85 | end 86 | links[nr] = 'act' 87 | return std.string.format("", xref)..r.."" 88 | end 89 | 90 | local iface_cmd = function(_, inp) 91 | local cmd = std.cmd_parse(inp) 92 | if std.debug_input then 93 | std.dprint("* input: ", inp) 94 | end 95 | if not cmd then 96 | return "Error in cmd arguments", false 97 | end 98 | 99 | std.cmd = cmd 100 | std.cache = {} 101 | local r, v = std.ref 'game':cmd(cmd) 102 | if r == true and v == false then 103 | return nil, true -- hack for menu mode 104 | end 105 | r = iface:fmt(r, v) -- to force fmt 106 | if std.debug_output then 107 | std.dprint("* output: ", r, v) 108 | end 109 | return r, v 110 | end; 111 | 112 | function iface:cmd(inp) 113 | local hdr = '' 114 | local a = std.split(inp) 115 | if a[1] == 'act' or a[1] == 'use' or a[1] == 'go' then 116 | if a[1] == 'use' then 117 | local use = std.split(a[2], ',') 118 | -- if use[2] then 119 | -- hdr = std.string.format("%s -> %s\n", use[1], use[2]) 120 | -- end 121 | for i = 1, 2 do 122 | local u = std.tonum(use[i]) 123 | if u then 124 | use[i] = dict[u] 125 | end 126 | end 127 | a[2] = std.join(use, ',') 128 | elseif std.tonum(a[2]) then 129 | -- hdr = std.string.format("%s\n", a[2]) 130 | a[2] = dict[std.tonum(a[2])] 131 | end 132 | inp = std.join(a) 133 | end 134 | local r, v = iface_cmd(self, inp) 135 | if type(r) == 'string' then r = hdr .. r end 136 | return r, v 137 | end 138 | 139 | local old_input = iface.input 140 | local shift = false 141 | local item = false 142 | function iface:input(event, a, b, ...) 143 | if event == 'kbd' and b:find 'shift' then 144 | shift = a 145 | if not shift then 146 | item = false 147 | end 148 | end 149 | if event == 'kbd' and a == false and xrm[b] then 150 | local nr = xrm[b] 151 | if links[nr] then 152 | if shift and not item then 153 | item = nr 154 | elseif shift then 155 | local o = item 156 | item = false 157 | shift = false 158 | return std.string.format("use %d,%d", o, nr) 159 | else 160 | shift = false 161 | return links[nr]..' '..nr 162 | end 163 | else 164 | item = false 165 | shift = false 166 | end 167 | end 168 | return old_input(iface, event, a, b, ...) 169 | end 170 | 171 | std.mod_start(function() 172 | local col = theme.get('win.col.fg') 173 | theme.set('win.col.link', col) 174 | theme.set('win.col.alink', col) 175 | col = theme.get('inv.col.fg') 176 | theme.set('inv.col.link', col) 177 | theme.set('inv.col.alink', col) 178 | 179 | dict = {} 180 | links = {} 181 | end) 182 | 183 | std.mod_step(function(state) 184 | if state then 185 | dict = {} 186 | links = {} 187 | item = false 188 | shift = false 189 | end 190 | end) 191 | 192 | load_fn() 193 | end 194 | --------------------------------------------------------------------------------