├── .gitignore ├── LICENSE ├── README.md ├── data └── hax │ ├── alchemy.lua │ ├── cheatgui.lua │ ├── console.lua │ ├── fungal.lua │ ├── gun_builder.lua │ ├── lib │ ├── json.lua │ └── pollnet.lua │ ├── materials.lua │ ├── spawnables.lua │ ├── special_spawnables.lua │ ├── superhackykb.lua │ ├── utils.lua │ ├── wand_empty.xml │ ├── wand_hax.lua │ └── wand_hax.xml ├── gen_spawnlist.py ├── init.lua ├── mod.xml ├── screenshot.jpg └── www ├── css ├── codemirror-index.css └── themes │ ├── dracula.css │ └── eclipse.css ├── index.html ├── js └── noitaconsole.js └── lib ├── codemirror.css ├── codemirror.js ├── jquery-2.2.2.min.js ├── modes └── lua │ ├── index.html │ └── lua.js ├── xterm-addon-fit.js ├── xterm.css └── xterm.js /.gitignore: -------------------------------------------------------------------------------- 1 | workshop.xml 2 | workshop_preview_image.png 3 | mod_id.txt 4 | workshop_id.txt 5 | compatibility.xml 6 | *.dll 7 | token.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 probable-basilisk 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.md: -------------------------------------------------------------------------------- 1 | ![Screenshot of the cheat menu as it appears in Noita](/screenshot.jpg?raw=true) 2 | 3 | # Noita Cheat GUI 4 | A basic in-game cheat menu. Note: if you just want to see the alchemy recipes without all the other cheat functionality, [there is a mod for that](https://github.com/probable-basilisk/alchemyrecipes). 5 | 6 | ## Installation 7 | 8 | You can either download the mod manually or clone this Git repo into the Noita `mods` sub-directory. 9 | 10 | ### (Recommended: opt-in to the Steam beta branch of Noita) 11 | Cheatgui is developed against, and really only tested with, the beta branch. It'll _probably_ work with the non-beta, 12 | but it's not guaranteed. 13 | 14 | ### Download manually 15 | 16 | [Download the release .zip](https://github.com/probable-basilisk/cheatgui/releases/download/v1.3.0/cheatgui_v1_3_0_beta.zip), 17 | and extract into your `Noita/mods/` directory, renaming the folder to just `cheatgui`. 18 | 19 | **IMPORTANT**: The naming of the installation directory matters-- **this README should end up in `Noita/mods/cheatgui/README.md`**. 20 | 21 | ### (or) Clone the Git repo 22 | 23 | You can git clone this repo directly into mods: 24 | 25 | ``` 26 | cd {your Noita install dir}/mods/ 27 | git clone https://github.com/probable-basilisk/cheatgui.git 28 | ``` 29 | 30 | **IMPORTANT**: You will need to get the `pollnet.dll` binary from the [release zip](https://github.com/probable-basilisk/cheatgui/releases/download/v1.3.0/cheatgui_v1_3_0_beta.zip), located in `bin/pollnet.dll`. You should copy this file to `Noita/mods/cheatgui/bin/pollnet.dll`. 31 | 32 | ### Enable the mod in Noita 33 | 34 | Enable the 'cheatgui' mod through the in-game pause menu. 35 | 36 | You will be prompted that "This mod has requested extra privileges." – see the following section 'Note about scary warnings' for details. 37 | 38 | #### Note about scary warnings 39 | 40 | Cheatgui needs unsafe access to allow typing, and to enable the web console. If the warnings bother you, get the Steam workshop version, although 41 | you will obviously miss out on the type-to-filter and webconsole functionality. 42 | 43 | ## Note about paths 44 | 45 | Right now I'm having the mod put all its files into the global `data/hax/` 46 | path rather than into the mod-specific path, both because I'm lazy, and 47 | also because I might want to cross-load some of these files from other things. 48 | -------------------------------------------------------------------------------- /data/hax/alchemy.lua: -------------------------------------------------------------------------------- 1 | local function hax_prng_next(v) 2 | local hi = math.floor(v / 127773.0) 3 | local lo = v % 127773 4 | v = 16807 * lo - 2836 * hi 5 | if v <= 0 then 6 | v = v + 2147483647 7 | end 8 | return v 9 | end 10 | 11 | local function shuffle(arr, seed) 12 | local v = math.floor(seed / 2) + 0x30f6 13 | v = hax_prng_next(v) 14 | for i = #arr, 1, -1 do 15 | v = hax_prng_next(v) 16 | local fidx = v / 2^31 17 | local target = math.floor(fidx * i) + 1 18 | arr[i], arr[target] = arr[target], arr[i] 19 | end 20 | end 21 | 22 | local LIQUIDS = {"acid", 23 | "alcohol", 24 | "blood", 25 | "blood_fungi", 26 | "blood_worm", 27 | "cement", 28 | "lava", 29 | "magic_liquid_berserk", 30 | "magic_liquid_charm", 31 | "magic_liquid_faster_levitation", 32 | "magic_liquid_faster_levitation_and_movement", 33 | "magic_liquid_invisibility", 34 | "magic_liquid_mana_regeneration", 35 | "magic_liquid_movement_faster", 36 | "magic_liquid_protection_all", 37 | "magic_liquid_teleportation", 38 | "magic_liquid_unstable_polymorph", 39 | "magic_liquid_unstable_teleportation", 40 | "magic_liquid_worm_attractor", 41 | "material_confusion", 42 | "mud", 43 | "oil", 44 | "poison", 45 | "radioactive_liquid", 46 | "swamp", 47 | "urine" , 48 | "water", 49 | "water_ice", 50 | "water_swamp", 51 | "magic_liquid_random_polymorph"} 52 | 53 | local ORGANICS = {"bone", 54 | "brass", 55 | "coal", 56 | "copper", 57 | "diamond", 58 | "fungi", 59 | "gold", 60 | "grass", 61 | "gunpowder", 62 | "gunpowder_explosive", 63 | "rotten_meat", 64 | "sand", 65 | "silver", 66 | "slime", 67 | "snow", 68 | "soil", 69 | "wax", 70 | "honey"} 71 | 72 | local function copy_arr(arr) 73 | local ret = {} 74 | for k, v in pairs(arr) do ret[k] = v end 75 | return ret 76 | end 77 | 78 | local function random_material(v, mats) 79 | for _ = 1, 1000 do 80 | v = hax_prng_next(v) 81 | local rval = v / 2^31 82 | local sel_idx = math.floor(#mats * rval) + 1 83 | local selection = mats[sel_idx] 84 | if selection then 85 | mats[sel_idx] = false 86 | return v, selection 87 | end 88 | end 89 | end 90 | 91 | local function random_recipe(rand_state, seed) 92 | local liqs = copy_arr(LIQUIDS) 93 | local orgs = copy_arr(ORGANICS) 94 | local m1, m2, m3, m4 = "?", "?", "?", "?" 95 | rand_state, m1 = random_material(rand_state, liqs) 96 | rand_state, m2 = random_material(rand_state, liqs) 97 | rand_state, m3 = random_material(rand_state, liqs) 98 | rand_state, m4 = random_material(rand_state, orgs) 99 | local combo = {m1, m2, m3, m4} 100 | 101 | rand_state = hax_prng_next(rand_state) 102 | local prob = 10 + math.floor((rand_state / 2^31) * 91) 103 | rand_state = hax_prng_next(rand_state) 104 | 105 | shuffle(combo, seed) 106 | return rand_state, {combo[1], combo[2], combo[3]}, prob 107 | end 108 | 109 | function get_alchemy() 110 | local seed = tonumber(StatsGetValue("world_seed")) 111 | local rand_state = math.floor(seed * 0.17127000 + 1323.59030000) 112 | 113 | for i = 1, 6 do 114 | rand_state = hax_prng_next(rand_state) 115 | end 116 | 117 | local lc_combo, ap_combo = {"?"}, {"?"} 118 | rand_state, lc_combo, lc_prob = random_recipe(rand_state, seed) 119 | rand_state, ap_combo, ap_prob = random_recipe(rand_state, seed) 120 | 121 | return lc_combo, ap_combo, lc_prob, ap_prob 122 | end -------------------------------------------------------------------------------- /data/hax/cheatgui.lua: -------------------------------------------------------------------------------- 1 | dofile_once("data/scripts/lib/coroutines.lua") 2 | dofile_once("data/scripts/lib/utilities.lua") 3 | dofile_once("data/scripts/perks/perk.lua") 4 | dofile_once("data/scripts/gun/gun_actions.lua") 5 | dofile_once("data/hax/materials.lua") 6 | dofile_once("data/hax/alchemy.lua") 7 | dofile_once("data/hax/spawnables.lua") 8 | dofile_once("data/hax/special_spawnables.lua") 9 | dofile_once("data/hax/fungal.lua") 10 | dofile_once("data/hax/gun_builder.lua") 11 | dofile_once("data/hax/superhackykb.lua") 12 | dofile_once("data/hax/utils.lua") 13 | 14 | local CHEATGUI_VERSION = "1.5.0" 15 | local CHEATGUI_TITLE = "cheatgui " .. CHEATGUI_VERSION 16 | local console_connected = false 17 | 18 | if _keyboard_present then 19 | -- have FFI 20 | dofile_once("data/hax/console.lua") 21 | else 22 | CHEATGUI_TITLE = CHEATGUI_TITLE .. "S" 23 | end 24 | 25 | local created_gui = false 26 | 27 | local _next_available_id = 100 28 | local function reset_id() 29 | _next_available_id = 100 30 | end 31 | local function next_id(n) 32 | n = n or 1 33 | local ret = _next_available_id 34 | _next_available_id = _next_available_id + n 35 | return ret 36 | end 37 | 38 | local _type_target = nil 39 | local _shift_target = nil 40 | 41 | local function handle_typing() 42 | local type_target = _type_target 43 | local req_shift = false 44 | if type_target == nil then 45 | type_target = _shift_target 46 | req_shift = true 47 | end 48 | if not type_target then return end 49 | local prev_val = type_target.value 50 | local hit_enter = false 51 | type_target.value, hit_enter = hack_type(prev_val, not req_shift) 52 | if (prev_val ~= type_target.value) and (type_target.on_change) then 53 | type_target:on_change() 54 | end 55 | if hit_enter and (type_target.on_hit_enter) then 56 | type_target:on_hit_enter() 57 | end 58 | end 59 | 60 | local function set_type_target(target) 61 | if not _keyboard_present then return end 62 | if _type_target and _type_target.on_lose_focus then 63 | _type_target:on_lose_focus() 64 | end 65 | _type_target = target 66 | if _type_target and _type_target.on_gain_focus then 67 | _type_target:on_gain_focus() 68 | end 69 | end 70 | 71 | local function set_type_default(target) 72 | _shift_target = target 73 | end 74 | 75 | if not _cheat_gui then 76 | print("Creating cheat GUI") 77 | _cheat_gui = GuiCreate() 78 | _gui_frame_function = nil 79 | created_gui = true 80 | else 81 | print("Reloading onto existing GUI") 82 | end 83 | 84 | local gui = _cheat_gui 85 | 86 | local closed_panel, perk_panel, cards_panel, menu_panel, flasks_panel 87 | local wands_panel, builder_panel, always_cast_panel, teleport_panel, info_panel 88 | local health_panel, money_panel, spawn_panel, console_panel 89 | 90 | local function Panel(options) 91 | if not options.name then 92 | options.name = options[1] 93 | end 94 | if not options.func then 95 | options.func = options[2] 96 | end 97 | return options 98 | end 99 | 100 | local panel_stack = {} 101 | local _active_panel = nil 102 | 103 | local function _change_active_panel(panel) 104 | if panel == _active_panel then return end 105 | set_type_default(nil) 106 | set_type_target(nil) 107 | _gui_frame_function = panel.func 108 | end 109 | 110 | local function prev_panel() 111 | if #panel_stack < 2 then 112 | _change_active_panel(closed_panel) 113 | panel_stack = {} 114 | else 115 | -- pop off last panel 116 | panel_stack[#panel_stack] = nil 117 | _change_active_panel(panel_stack[#panel_stack]) 118 | end 119 | end 120 | 121 | local function jump_back_panel(idx) 122 | if #panel_stack <= idx then return end 123 | for i = idx+1, #panel_stack do 124 | panel_stack[i] = nil 125 | end 126 | _change_active_panel(panel_stack[#panel_stack]) 127 | end 128 | 129 | local function enter_panel(panel) 130 | panel_stack[#panel_stack+1] = panel 131 | _change_active_panel(panel) 132 | end 133 | 134 | local function hide_gui() 135 | _change_active_panel(closed_panel) 136 | end 137 | 138 | local function goto_subpanel(panel) 139 | panel_stack = {} 140 | enter_panel(menu_panel) 141 | enter_panel(panel) 142 | end 143 | 144 | local function show_gui() 145 | if #panel_stack == 0 then 146 | enter_panel(menu_panel) 147 | else 148 | _change_active_panel(panel_stack[#panel_stack]) 149 | end 150 | end 151 | 152 | local function breadcrumbs(x, y) 153 | GuiLayoutBeginHorizontal(gui, x, y) 154 | if GuiButton( gui, 0, 0, "[-]", next_id()) then 155 | hide_gui() 156 | end 157 | for idx, panel in ipairs(panel_stack) do 158 | if GuiButton( gui, 0, 0, panel.name .. ">", next_id()) then 159 | jump_back_panel(idx) 160 | end 161 | end 162 | GuiLayoutEnd(gui) 163 | GuiLayoutBeginHorizontal( gui, x, y+3) 164 | if #panel_stack > 1 and GuiButton( gui, 0, 0, "< back", next_id()) then 165 | prev_panel() 166 | end 167 | GuiLayoutEnd( gui ) 168 | end 169 | 170 | local _info_widgets = {} 171 | local _sorted_info_widgets = {} 172 | local _all_info_widgets = {} 173 | 174 | local function _update_info_widgets() 175 | _sorted_info_widgets = {} 176 | for wname, widget in pairs(_info_widgets) do 177 | table.insert(_sorted_info_widgets, {wname, widget}) 178 | end 179 | table.sort(_sorted_info_widgets, function(a, b) 180 | return a[1] < b[1] 181 | end) 182 | end 183 | 184 | local function add_info_widget(wname, w) 185 | _info_widgets[wname] = w 186 | _update_info_widgets() 187 | end 188 | 189 | local function remove_info_widget(wname) 190 | _info_widgets[wname] = nil 191 | _update_info_widgets() 192 | end 193 | 194 | local function register_widget(wname, w) 195 | table.insert(_all_info_widgets, {wname, w}) 196 | end 197 | 198 | closed_panel = Panel{"[+]", function() 199 | GuiLayoutBeginHorizontal( gui, 1, 0 ) 200 | if GuiButton( gui, 0, 0, "[+]", next_id() ) then 201 | show_gui() 202 | end 203 | GuiLayoutEnd( gui ) 204 | local col_pos = 5 205 | for idx, winfo in ipairs(_sorted_info_widgets) do 206 | local wname, widget = unpack(winfo) 207 | GuiLayoutBeginHorizontal(gui, col_pos, 0) 208 | local text = widget:text() 209 | if idx > 1 then text = "| " .. text end 210 | if GuiButton( gui, 0, 0, text, next_id() ) then 211 | widget:on_click() 212 | end 213 | GuiLayoutEnd( gui ) 214 | col_pos = col_pos + (widget.width or 10) 215 | end 216 | end} 217 | 218 | local function wrap_spawn(path) 219 | return function() spawn_item(path) end 220 | end 221 | 222 | local function maybe_call(s_or_f, opt) 223 | if type(s_or_f) == 'function' then 224 | return s_or_f(opt) 225 | else 226 | return s_or_f 227 | end 228 | end 229 | 230 | local function get_option_text(opt) 231 | return maybe_call(opt.text or opt[1], opt) 232 | end 233 | 234 | local function grid_layout(options, col_width, callback) 235 | local num_options = #options 236 | local col_size = 28 237 | local ncols = math.ceil(num_options / col_size) 238 | local xoffset = col_width or 25 239 | local xpos = 5 240 | local opt_pos = 1 241 | for col = 1, ncols do 242 | if not options[opt_pos] then break end 243 | GuiLayoutBeginVertical( gui, xpos, 11 ) 244 | for row = 1, col_size do 245 | if not options[opt_pos] then break end 246 | local opt = options[opt_pos] 247 | local text = get_option_text(opt) 248 | if GuiButton( gui, 0, 0, text, next_id() ) then 249 | (callback or opt.f or opt[2])(opt) 250 | end 251 | opt_pos = opt_pos + 1 252 | end 253 | GuiLayoutEnd( gui) 254 | xpos = xpos + xoffset 255 | end 256 | end 257 | 258 | local function grid_panel(title, options, col_width, callback) 259 | breadcrumbs(1, 0) 260 | grid_layout(options, col_width, callback) 261 | end 262 | 263 | local function filter_options(options, str) 264 | local ret = {} 265 | for _, opt in ipairs(options) do 266 | local text = maybe_call(opt.text, opt):lower() 267 | if text:find(str) then 268 | table.insert(ret, opt) 269 | end 270 | end 271 | return ret 272 | end 273 | 274 | local function create_radio(title, options, default, x_spacing) 275 | if not default then default = options[1][2] end 276 | local selected = default --1 277 | -- for i, v in ipairs(options) do 278 | -- if v[2] == default then selected = i end 279 | -- end 280 | local wrapper = { 281 | index = selected, 282 | value = options[selected][2], 283 | reset = function(_self) 284 | _self.index = default 285 | _self.value = options[default][2] 286 | end 287 | } 288 | return function(xpos, ypos) 289 | GuiLayoutBeginHorizontal(gui, xpos, ypos) 290 | GuiText(gui, 0, 0, title) 291 | GuiLayoutEnd(gui) 292 | GuiLayoutBeginHorizontal(gui, xpos+(x_spacing or 12), ypos) 293 | for idx, option in ipairs(options) do 294 | local text = option[1] 295 | if idx == wrapper.index then text = "[" .. text .. "]" end 296 | if GuiButton( gui, 0, 0, text, next_id() ) then 297 | wrapper.index = idx 298 | wrapper.value = option[2] 299 | end 300 | end 301 | GuiLayoutEnd(gui) 302 | end, wrapper 303 | end 304 | 305 | local function alphabetize(options, do_it) 306 | if not do_it then return options end 307 | local keys = {} 308 | for idx, opt in ipairs(options) do 309 | keys[idx] = {get_option_text(opt):lower(), opt} 310 | end 311 | table.sort(keys, function(a, b) return a[1] < b[1] end) 312 | local sorted = {} 313 | for idx, v in ipairs(keys) do 314 | sorted[idx] = v[2] 315 | end 316 | return sorted 317 | end 318 | 319 | local alphabetize_widget, alphabetize_val = create_radio("Alphabetize:", { 320 | {"Yes", true}, {"No", false} 321 | }, 2, 16) 322 | 323 | local function breakup_pages(options, page_size) 324 | local pages = {} 325 | local npages = math.ceil(#options / page_size) 326 | local opt_pos = 1 327 | for page = 1, npages do 328 | if not options[opt_pos] then break end 329 | pages[page] = {} 330 | for idx = 1, page_size do 331 | if not options[opt_pos] then break end 332 | table.insert(pages[page], options[opt_pos]) 333 | opt_pos = opt_pos + 1 334 | end 335 | end 336 | return pages 337 | end 338 | 339 | local function wrap_paginate(title, options, page_size, callback) 340 | page_size = page_size or 28*4 341 | local cur_page = 1 342 | local pages = breakup_pages(options, page_size) 343 | 344 | local prev_alphabetize = false 345 | local filtered_set = options 346 | local filter_thing = { 347 | value = "", on_change = function(_self) 348 | filtered_set = alphabetize( 349 | filter_options(options, _self.value), 350 | alphabetize_val.value 351 | ) 352 | end 353 | } 354 | return function(force_refilter) 355 | if force_refilter or (prev_alphabetize ~= alphabetize_val.value) then 356 | force_refilter = true 357 | pages = breakup_pages( 358 | alphabetize(options, alphabetize_val.value), page_size 359 | ) 360 | end 361 | prev_alphabetize = alphabetize_val.value 362 | set_type_default(filter_thing) 363 | local filter_str = filter_thing.value 364 | local filter_text = "[shift+type to filter]" 365 | if filter_str and (filter_str ~= "") then 366 | filter_text = filter_str 367 | end 368 | 369 | if _keyboard_present then 370 | GuiLayoutBeginVertical( gui, 61, 0) 371 | GuiText(gui, 0, 0, "Filter:") 372 | GuiLayoutEnd( gui ) 373 | GuiLayoutBeginVertical( gui, 61 + 11, 0 ) 374 | if GuiButton( gui, 0, 0, filter_text, next_id() ) then 375 | filter_thing.value = "" 376 | end 377 | GuiLayoutEnd( gui) 378 | end 379 | alphabetize_widget(31, 0) 380 | 381 | if (not filter_str) or (filter_str == "") then 382 | grid_panel(title, pages[cur_page], nil, callback) 383 | if cur_page > 1 then 384 | GuiLayoutBeginHorizontal(gui, 46, 96) 385 | if GuiButton( gui, 0, 0, "<-", next_id() ) then 386 | cur_page = cur_page - 1 387 | end 388 | GuiLayoutEnd(gui) 389 | end 390 | if #pages > 1 then 391 | GuiLayoutBeginHorizontal(gui, 48, 96) 392 | GuiText( gui, 0, 0, ("%d/%d"):format(cur_page, #pages)) 393 | GuiLayoutEnd(gui) 394 | end 395 | if cur_page < #pages then 396 | GuiLayoutBeginHorizontal(gui, 51, 96) 397 | if GuiButton( gui, 0, 0, "->", next_id() ) then 398 | cur_page = cur_page + 1 399 | end 400 | GuiLayoutEnd(gui) 401 | end 402 | else 403 | if force_refilter then 404 | filtered_set = alphabetize( 405 | filter_options(options, filter_str), 406 | alphabetize_val.value 407 | ) 408 | end 409 | grid_panel(title, filtered_set, nil, callback) 410 | end 411 | end 412 | end 413 | 414 | local num_types = { 415 | float = {function(x) return x end, "%0.2f", 1.0}, 416 | int = {function(x) return round(x) end, "%d", 1.0}, 417 | frame = {function(x) return round(x) end, "%0.2f", 1.0/60.0}, 418 | mills = {function(x) return round(x) end, "%0.2f", 1.0/1000.0}, 419 | hearts = {function(x) return x end, "%d", 25.0} 420 | } 421 | 422 | local function create_numerical(title, increments, default, kind) 423 | local validate, fstr, multiplier = unpack(num_types[kind or "float"]) 424 | 425 | local text_wrapper = { 426 | value = "", 427 | on_change = function(_self) 428 | -- eh? 429 | end, 430 | on_gain_focus = function(_self) 431 | _self.has_focus = true 432 | _self.value = _self.numeric:display_val() 433 | end, 434 | set_value = function(_self) 435 | local temp = tonumber(_self.value) 436 | if temp then 437 | _self.numeric.value = validate(temp / multiplier) 438 | end 439 | end, 440 | on_lose_focus = function(_self) 441 | _self.has_focus = false 442 | _self:set_value() 443 | end, 444 | on_hit_enter = function(_self) 445 | _self:set_value() 446 | set_type_target(nil) 447 | end, 448 | display_val = function(_self) 449 | if not _self.has_focus then return nil end 450 | return _self.value .. "_" 451 | end 452 | } 453 | 454 | local wrapper = { 455 | text = text_wrapper, 456 | value = default or 0.0, 457 | display_val = function(_self) 458 | return fstr:format(_self.value * multiplier) 459 | end, 460 | temp_val = "", 461 | reset = function(_self) 462 | _self.value = default 463 | end 464 | } 465 | 466 | text_wrapper.numeric = wrapper 467 | 468 | return function(xpos, ypos) 469 | GuiLayoutBeginHorizontal(gui, xpos, ypos) 470 | GuiText(gui, 0, 0, title) 471 | GuiLayoutEnd(gui) 472 | GuiLayoutBeginHorizontal(gui, xpos + 12, ypos) 473 | for idx = #increments, 1, -1 do 474 | local s = "[" .. string.rep("-", idx) .. "]" 475 | if GuiButton( gui, 0, 0, s, next_id() ) then 476 | wrapper.value = wrapper.value - increments[idx] 477 | end 478 | end 479 | if GuiButton(gui, 0, 0, "" .. (text_wrapper:display_val() or wrapper:display_val()), next_id() ) then 480 | if text_wrapper.has_focus then 481 | set_type_target(nil) 482 | else 483 | set_type_target(text_wrapper) 484 | end 485 | end 486 | for idx = 1, #increments do 487 | local s = "[" .. string.rep("+", idx) .. "]" 488 | if GuiButton( gui, 0, 0, s, next_id() ) then 489 | wrapper.value = wrapper.value + increments[idx] 490 | end 491 | end 492 | GuiLayoutEnd(gui) 493 | end, wrapper 494 | end 495 | 496 | local localization_widget, localization_val = create_radio("Show localized names:", { 497 | {"Yes", true}, {"No", false} 498 | }, 2, 16) 499 | 500 | local shuffle_widget, shuffle_val = create_radio("Shuffle", { 501 | {"Yes", true}, {"No", false} 502 | }, 2) 503 | 504 | local mana_widget, mana_val = create_numerical("Mana", {50, 500}, 300, 'int') 505 | local mana_rec_widget, mana_rec_val = create_numerical("Mana Recharge", {10, 100}, 100, 'int') 506 | local slots_widget, slots_val = create_numerical("Slots", {1, 5}, 5, 'int') 507 | local multi_widget, multi_val = create_numerical("Multicast", {1}, 1, 'int') 508 | local reload_widget, reload_val = create_numerical("Reload", {1, 10}, 30, 'frame') 509 | local delay_widget, delay_val = create_numerical("Delay", {1, 10}, 30, 'frame') 510 | local spread_widget, spread_val = create_numerical("Spread", {0.1, 1}, 0.0, 'float') 511 | local speed_widget, speed_val = create_numerical("Speed", {0.01, 0.1}, 1.0, 'float') 512 | 513 | --local always_cast_choice = nil 514 | local MAX_ALWAYS_CASTS=10 -- eh 515 | local always_cast_index = 1 516 | local always_casts = {} 517 | local function compact_always_casts() 518 | local new_always_casts = {} 519 | for idx = 1, MAX_ALWAYS_CASTS do 520 | if always_casts[idx] then 521 | table.insert(new_always_casts, always_casts[idx]) 522 | end 523 | end 524 | always_casts = new_always_casts 525 | end 526 | 527 | local builder_widgets = { 528 | {shuffle_widget, shuffle_val}, 529 | {mana_widget, mana_val}, 530 | {mana_rec_widget, mana_rec_val}, 531 | {slots_widget, slots_val}, 532 | {multi_widget, multi_val}, 533 | {reload_widget, reload_val}, 534 | {delay_widget, delay_val}, 535 | {spread_widget, spread_val}, 536 | {speed_widget, speed_val} 537 | } 538 | 539 | builder_panel = Panel{"wand builder", function() 540 | breadcrumbs(1, 0) 541 | 542 | for idx, widget in ipairs(builder_widgets) do 543 | widget[1](1, 8 + idx*4) 544 | end 545 | 546 | GuiLayoutBeginVertical(gui, 1, 48) 547 | for idx = 1, MAX_ALWAYS_CASTS do 548 | local label = "Always cast" 549 | if idx > 1 then label = label .. " (" .. idx .. ")" end 550 | if GuiButton( gui, 0, 0, label .. ": " .. (always_casts[idx] or "None"), next_id() ) then 551 | always_cast_index = idx 552 | enter_panel(always_cast_panel) 553 | end 554 | if not always_casts[idx] then break end 555 | end 556 | if GuiButton( gui, 0, 0, "[Reset all]", next_id() ) then 557 | for _, widget in ipairs(builder_widgets) do 558 | widget[2]:reset() 559 | end 560 | always_casts = {} 561 | always_cast_index=1 562 | end 563 | if GuiButton( gui, 0, 4, "[Spawn]", next_id() ) then 564 | local x, y = get_player_pos() 565 | local gun = { 566 | deck_capacity = slots_val.value, 567 | actions_per_round = multi_val.value, 568 | reload_time = reload_val.value, 569 | shuffle_deck_when_empty = (shuffle_val.value and 1) or 0, 570 | fire_rate_wait = delay_val.value, 571 | spread_degrees = spread_val.value, 572 | speed_multiplier = speed_val.value, 573 | mana_max = mana_val.value, 574 | mana_charge_speed = mana_rec_val.value, 575 | always_casts = always_casts --always_cast_choice 576 | } 577 | build_gun(x, y, gun) 578 | end 579 | GuiLayoutEnd(gui) 580 | end} 581 | 582 | local xpos_widget, xpos_val = create_numerical("X", {100, 1000, 10000}, 0, 'int') 583 | local ypos_widget, ypos_val = create_numerical("Y", {100, 1000, 10000}, 0, 'int') 584 | 585 | local SPECIAL_LOCATIONS = { 586 | ["$biome_lava"] = {x=2300} 587 | } 588 | 589 | local quick_teleports = nil 590 | local function find_quick_teleports() 591 | if quick_teleports then return quick_teleports end 592 | quick_teleports = {} 593 | local temp_mountains = {} 594 | local prev_biome = "?" 595 | for y = 0, 15000, 500 do 596 | local cur_biome = BiomeMapGetName(0, y) 597 | if cur_biome == "$biome_holymountain" then 598 | temp_mountains[prev_biome] = y 599 | else 600 | prev_biome = cur_biome 601 | end 602 | end 603 | local function refine_position(y0) 604 | for y = y0, y0+500, 10 do 605 | local cur_biome = BiomeMapGetName(0, y) 606 | if cur_biome ~= "$biome_holymountain" then 607 | return y-10, cur_biome 608 | end 609 | end 610 | end 611 | local mountains = {} 612 | for biome, y in pairs(temp_mountains) do 613 | local teleport_y, next_biome = refine_position(y) 614 | teleport_y = teleport_y-200 615 | local teleport_x = -200 616 | if SPECIAL_LOCATIONS[next_biome] then 617 | teleport_x = SPECIAL_LOCATIONS[next_biome].x or teleport_x 618 | teleport_y = SPECIAL_LOCATIONS[next_biome].y or teleport_y 619 | end 620 | local label = ("%s (%d, %d)"):format(GameTextGet(next_biome), teleport_x, teleport_y) 621 | table.insert(quick_teleports, {label, teleport_x, teleport_y}) 622 | end 623 | table.sort(quick_teleports, function(a, b) return a[3] < b[3] end) 624 | return quick_teleports 625 | end 626 | 627 | teleport_panel = Panel{"teleport", function() 628 | xpos_widget(1, 12) 629 | ypos_widget(1, 16) 630 | 631 | breadcrumbs(1, 0) 632 | 633 | GuiLayoutBeginVertical(gui, 1, 20) 634 | if GuiButton( gui, 0, 0, "[Get current position]", next_id() ) then 635 | local x, y = get_player_pos() 636 | xpos_val.value, ypos_val.value = math.floor(x), math.floor(y) 637 | end 638 | if GuiButton( gui, 0, 0, "[Zero position]", next_id() ) then 639 | xpos_val.value, ypos_val.value = 0, 0 640 | end 641 | if GuiButton( gui, 0, 0, "[Teleport]", next_id() ) then 642 | GamePrint(("Attempting to teleport to (%d, %d)"):format(xpos_val.value, ypos_val.value)) 643 | teleport(xpos_val.value, ypos_val.value) 644 | end 645 | GuiText(gui, 0, 0, " ") -- just a spacer 646 | GuiText(gui, 0, 0, "----Quick Teleports----") 647 | for i, location in ipairs(find_quick_teleports()) do 648 | local label, x, y = unpack(location) 649 | if GuiButton(gui, 0, 0, label, next_id() ) then 650 | GamePrint(("Attempting to teleport to (%d, %d)"):format(x, y)) 651 | teleport(x, y) 652 | end 653 | end 654 | GuiLayoutEnd(gui) 655 | end} 656 | 657 | local cur_hp_widget, cur_hp_val = create_numerical("HP", {1, 4}, 4, 'hearts') 658 | local max_hp_widget, max_hp_val = create_numerical("Max HP", {1, 4}, 4, 'hearts') 659 | 660 | health_panel = Panel{"health", function() 661 | cur_hp_widget(1, 12) 662 | max_hp_widget(1, 16) 663 | 664 | breadcrumbs(1, 0) 665 | 666 | GuiLayoutBeginVertical(gui, 1, 20) 667 | if GuiButton( gui, 0, 0, "[Get current health]", next_id() ) then 668 | cur_hp_val.value, max_hp_val.value = get_health() 669 | end 670 | if GuiButton( gui, 0, 0, "[Apply health changes]", next_id() ) then 671 | set_health(cur_hp_val.value, max_hp_val.value) 672 | end 673 | GuiText(gui, 0, 0, " ") -- just a spacer 674 | GuiText(gui, 0, 0, "----Quick health----") 675 | if GuiButton( gui, 0, 0, "[Add +25 max HP]", next_id() ) then 676 | cur_hp_val.value, max_hp_val.value = get_health() 677 | cur_hp_val.value, max_hp_val.value = cur_hp_val.value+1, max_hp_val.value+1 678 | set_health(cur_hp_val.value, max_hp_val.value) 679 | end 680 | if GuiButton( gui, 0, 0, "[Add +100 max HP]", next_id() ) then 681 | cur_hp_val.value, max_hp_val.value = get_health() 682 | cur_hp_val.value, max_hp_val.value = cur_hp_val.value+4, max_hp_val.value+4 683 | set_health(cur_hp_val.value, max_hp_val.value) 684 | end 685 | GuiLayoutEnd(gui) 686 | end} 687 | 688 | local money_widget, money_val = create_numerical("Gold", {10, 100, 1000}, 0, 'int') 689 | 690 | money_panel = Panel{"gold", function() 691 | money_widget(1, 12) 692 | breadcrumbs(1, 0) 693 | 694 | GuiLayoutBeginVertical(gui, 1, 20) 695 | if GuiButton( gui, 0, 0, "[Get current gold]", next_id() ) then 696 | money_val.value = get_money() 697 | end 698 | if GuiButton( gui, 0, 0, "[Set current gold]", next_id() ) then 699 | set_money(money_val.value) 700 | end 701 | GuiText(gui, 0, 0, " ") -- just a spacer 702 | GuiText(gui, 0, 0, "----Quick cash----") 703 | if GuiButton( gui, 0, 0, "[+100 Gold]", next_id() ) then 704 | money_val.value = get_money()+100 705 | set_money(money_val.value) 706 | end 707 | if GuiButton( gui, 0, 0, "[+500 Gold]", next_id() ) then 708 | money_val.value = get_money()+500 709 | set_money(money_val.value) 710 | end 711 | if GuiButton( gui, 0, 0, "[+2000 Gold]", next_id() ) then 712 | money_val.value = get_money()+2000 713 | set_money(money_val.value) 714 | end 715 | GuiLayoutEnd(gui) 716 | end} 717 | 718 | -- build these button lists once so we aren't rebuilding them every frame 719 | local function localized_name(thing) 720 | if localization_val.value then return thing.ui_name else return thing.id end 721 | end 722 | 723 | local function spawn_spell_button(card) 724 | local x, y = get_player_pos() 725 | GamePrint( "Attempting to spawn " .. card.id) 726 | CreateItemActionEntity( card.id, x, y ) 727 | end 728 | 729 | local function set_always_cast(card) 730 | always_casts[always_cast_index] = (card and card.id) or nil 731 | compact_always_casts() 732 | prev_panel() 733 | end 734 | 735 | local spell_options = {} 736 | local always_cast_options = { 737 | { 738 | text = "None", 739 | f = function() 740 | set_always_cast(nil) 741 | end 742 | } 743 | } 744 | 745 | for idx, card in ipairs(actions) do 746 | local ui_name = resolve_localized_name(card.name) 747 | local id = card.id:lower() 748 | if (not ui_name) or (ui_name == "") then ui_name = id end 749 | spell_options[idx] = { 750 | text = localized_name, 751 | id = id, ui_name = ui_name, 752 | f = spawn_spell_button 753 | } 754 | always_cast_options[idx+1] = { 755 | text = localized_name, 756 | id = id, ui_name = ui_name, 757 | f = set_always_cast 758 | } 759 | end 760 | 761 | local function spawn_perk_button(perk) 762 | GamePrint( "Attempting to spawn " .. perk.id) 763 | spawn_perk(perk.id, get_player()) 764 | end 765 | 766 | local perk_options = {} 767 | for idx, perk in ipairs(perk_list) do 768 | perk_options[idx] = { 769 | text = localized_name, 770 | id = perk.id, 771 | ui_name = resolve_localized_name(perk.ui_name, perk.id), 772 | f = spawn_perk_button 773 | } 774 | end 775 | 776 | local quantity_widget, quantity_val = create_numerical("Quantity mult:", {100, 1000}, 1000, 'mills') 777 | local container_widget, container_val = create_radio("Container:", { 778 | {"Potion", "potion"}, {"Pouch", "pouch"} 779 | }, 1) 780 | 781 | local function spawn_potion_button(potion) 782 | GamePrint( "Attempting to spawn potion of " .. potion.id) 783 | spawn_potion(potion.id, quantity_val.value, container_val.value) 784 | end 785 | 786 | local potion_options = {} 787 | for idx, matinfo in ipairs(materials_list) do 788 | local material, translated_material = unpack(matinfo) 789 | if material:sub(1,1) ~= "-" then 790 | potion_options[idx] = { 791 | text = localized_name, 792 | ui_name = translated_material, id = material, 793 | f = spawn_potion_button 794 | } 795 | else 796 | potion_options[idx] = {text = material, f = function() end} 797 | end 798 | end 799 | 800 | local wand_options = {} 801 | for i = 1, 5 do 802 | wand_options[i] = { 803 | "Wand Level " .. i, 804 | wrap_spawn("data/entities/items/wand_level_0" .. i .. ".xml") 805 | } 806 | end 807 | table.insert(wand_options, {"Haxx", wrap_spawn("data/hax/wand_hax.xml")}) 808 | 809 | local tourist_mode_on = false 810 | local function toggle_tourist_mode() 811 | tourist_mode_on = not tourist_mode_on 812 | set_tourist_mode(tourist_mode_on) 813 | GamePrint("Tourist mode: " .. tostring(tourist_mode_on)) 814 | end 815 | 816 | local function open_console() 817 | local auth_token = listen_console_connections() 818 | console_connected = true 819 | os.execute("start http://localhost:8777/index.html?token=" .. (auth_token or "none")) 820 | end 821 | 822 | local seedval = "?" 823 | SetRandomSeed(0, 0) 824 | seedval = tostring(Random() * 2^31) 825 | 826 | local LC, AP, LC_prob, AP_prob = get_alchemy() 827 | 828 | local function format_combo(combo, prob, localize) 829 | local ret = {} 830 | for idx, mat in ipairs(combo) do 831 | ret[idx] = (localize and localize_material(mat)) or mat 832 | end 833 | return table.concat(ret, ", ") .. " (" .. prob .. "%)" 834 | end 835 | 836 | local alchemy_combos = { 837 | AP = { 838 | [false]=format_combo(AP, AP_prob, false), 839 | [true]=format_combo(AP, AP_prob, true) 840 | }, 841 | LC = { 842 | [false]=format_combo(LC, LC_prob, false), 843 | [true]=format_combo(LC, LC_prob, true) 844 | } 845 | } 846 | 847 | local extra_buttons = {} 848 | function register_cheat_button(title, f) 849 | table.insert(extra_buttons, {title, f}) 850 | end 851 | 852 | local function draw_extra_buttons() 853 | for _, button in ipairs(extra_buttons) do 854 | local title, f = button[1], button[2] 855 | if type(title) == 'function' then title = title() end 856 | if f then 857 | if GuiButton( gui, 0, 0, title, next_id() ) then 858 | f() 859 | end 860 | else 861 | GuiText( gui, 0, 0, title) 862 | end 863 | end 864 | end 865 | 866 | local function wrap_localized(f) 867 | local prev_localization = false 868 | return function() 869 | localization_widget(31, 3) 870 | local localization_changed = (prev_localization ~= localization_val.value) 871 | prev_localization = localization_val.value 872 | f(localization_changed) 873 | end 874 | end 875 | 876 | local _flask_base = wrap_localized(wrap_paginate("Select a flask to spawn:", potion_options)) 877 | local function flask_panel_func() 878 | quantity_widget(61, 3) 879 | container_widget(31, 6) 880 | _flask_base() 881 | end 882 | 883 | local gui_grid_ref_panel = Panel{"gui grid ref.", function() 884 | breadcrumbs(1, 0) 885 | for row = 0, 100, 10 do 886 | for col = 0, 100, 10 do 887 | GuiLayoutBeginHorizontal(gui, col, row) 888 | GuiText(gui, 0, 0, ("(%d,%d)"):format(col, row)) 889 | GuiLayoutEnd(gui) 890 | end 891 | end 892 | end} 893 | 894 | local function spawn_item_button(item) 895 | GamePrint("Attempting to spawn " .. item.path) 896 | spawn_item(item.path) 897 | end 898 | 899 | -- merge special spawns into the base spawnlist 900 | for _, v in ipairs(special_spawnables) do 901 | table.insert(spawn_list, v) 902 | end 903 | 904 | -- generate spawn item options 905 | local spawn_options = {} 906 | for idx, item in ipairs(spawn_list) do 907 | spawn_options[idx] = { 908 | text = localized_name, 909 | path = item.path, 910 | id = item.xml, 911 | ui_name = item.name, 912 | f = spawn_item_button 913 | } 914 | end 915 | 916 | always_cast_panel = Panel{"always cast", wrap_localized(wrap_paginate("Select a spell: ", always_cast_options))} 917 | cards_panel = Panel{"spells", wrap_localized(wrap_paginate("Select a spell to spawn:", spell_options))} 918 | perk_panel = Panel{"perks", wrap_localized(wrap_paginate("Select a perk to spawn:", perk_options))} 919 | flasks_panel = Panel{"flasks", flask_panel_func} 920 | spawn_panel = Panel{"items", wrap_localized(wrap_paginate("Select an item to spawn:", spawn_options))} 921 | 922 | wands_panel = Panel{"wands", function() 923 | grid_panel("Select a wand to spawn:", wand_options) 924 | end} 925 | 926 | info_panel = Panel{"widgets", function() 927 | breadcrumbs(1, 0) 928 | GuiLayoutBeginVertical(gui, 1, 11) 929 | for idx, winfo in ipairs(_all_info_widgets) do 930 | local wname, w = unpack(winfo) 931 | local enabled = _info_widgets[wname] ~= nil 932 | local text = w:text() 933 | if enabled then 934 | if GuiButton(gui, 0, 0, "[*] " .. text, next_id() ) then 935 | remove_info_widget(wname) 936 | end 937 | else 938 | if GuiButton(gui, 0, 0, "[ ] " .. text, next_id() ) then 939 | GamePrint("Adding " .. wname .. " to info bar (minimize cheatgui to see)") 940 | add_info_widget(wname, w) 941 | end 942 | end 943 | end 944 | GuiLayoutEnd(gui) 945 | end} 946 | 947 | local fungal_conv = {from="blood", to="blood"} 948 | local fungal_index 949 | 950 | local function choose_fungal_material(mat) 951 | fungal_conv[fungal_index] = mat.id 952 | prev_panel() 953 | end 954 | 955 | local fungal_material_panel = Panel{"shift material", 956 | wrap_localized(wrap_paginate("Select a material: ", potion_options, nil, choose_fungal_material))} 957 | 958 | local function predict_nth_shift(n) 959 | local shift_from, shift_to = fungal_predict_transform(n or 0) 960 | if shift_from and shift_to then 961 | return tostring((shift_from or "?")) .. " -> " 962 | .. tostring((shift_to or "?")) 963 | else 964 | return "No effect (same material chosen as src and dest)" 965 | end 966 | end 967 | 968 | local fungal_panel = Panel{"fungal", function() 969 | breadcrumbs(1, 0) 970 | GuiLayoutBeginVertical(gui, 1, 12) 971 | GuiText(gui, 0, 0, "Next shift: " .. predict_nth_shift(0)) 972 | GuiText(gui, 0, 0, "Next shift+1: " .. predict_nth_shift(1)) 973 | GuiText(gui, 0, 0, "Next shift+2: " .. predict_nth_shift(2)) 974 | if GuiButton( gui, 0, 0, "FROM: " .. fungal_conv.from, next_id() ) then 975 | fungal_index = "from" 976 | enter_panel(fungal_material_panel) 977 | end 978 | if GuiButton( gui, 0, 0, "TO: " .. fungal_conv.to, next_id() ) then 979 | fungal_index = "to" 980 | enter_panel(fungal_material_panel) 981 | end 982 | if GuiButton( gui, 0, 0, "[Force convert]", next_id()) then 983 | GamePrint("Would convert: " .. fungal_conv.from .. " -> " .. fungal_conv.to) 984 | fungal_force_convert(fungal_conv.from, fungal_conv.to) 985 | end 986 | GuiLayoutEnd(gui) 987 | end} 988 | 989 | 990 | console_panel = Panel{"console", function() 991 | breadcrumbs(1, 0) 992 | GuiLayoutBeginVertical(gui, 1, 11) 993 | if console_connected then 994 | if GuiButton( gui, 0, 0, "[Close console host]", next_id() ) then 995 | close_console_connections() 996 | console_connected = false 997 | end 998 | else 999 | if GuiButton( gui, 0, 0, "[Open console host]", next_id() ) then 1000 | listen_console_connections() 1001 | console_connected = true 1002 | end 1003 | end 1004 | if GuiButton( gui, 0, 0, "[Open new console]", next_id() ) then 1005 | open_console() 1006 | end 1007 | GuiText(gui, 0, 0, " ") -- just a spacer 1008 | GuiText(gui, 0, 0, "----Active connections (click to close)----") 1009 | local conns = get_console_connections() 1010 | local sorted_conns = {} 1011 | for addr, client in pairs(conns) do 1012 | table.insert(sorted_conns, addr) 1013 | end 1014 | table.sort(sorted_conns) 1015 | for _, addr in ipairs(sorted_conns) do 1016 | local conn = conns[addr] or {stat_out=-1, stat_in=-1} 1017 | local text = ("%s [in: %d, out: %d]"):format(addr, conn.stat_in or 0, conn.stat_out or 0) 1018 | if GuiButton( gui, 0, 0, text, next_id() ) then 1019 | if conns[addr] then conns[addr]:close() end 1020 | end 1021 | end 1022 | GuiLayoutEnd(gui) 1023 | end} 1024 | 1025 | local main_panels = { 1026 | perk_panel, cards_panel, flasks_panel, wands_panel, spawn_panel, 1027 | builder_panel, health_panel, money_panel, 1028 | teleport_panel, fungal_panel, info_panel, gui_grid_ref_panel 1029 | } 1030 | 1031 | if _keyboard_present then table.insert(main_panels, console_panel) end 1032 | 1033 | local function draw_main_panels() 1034 | for idx, panel in ipairs(main_panels) do 1035 | if GuiButton( gui, 0, 0, panel.name .. "->", next_id() ) then 1036 | enter_panel(panel) 1037 | end 1038 | end 1039 | end 1040 | 1041 | menu_panel = Panel{CHEATGUI_TITLE, function() 1042 | breadcrumbs(1, 0) 1043 | GuiLayoutBeginVertical( gui, 1, 11 ) 1044 | draw_main_panels() 1045 | draw_extra_buttons() 1046 | GuiLayoutEnd(gui) 1047 | end} 1048 | 1049 | register_cheat_button("[edit wands everywhere]", function() 1050 | spawn_perk("EDIT_WANDS_EVERYWHERE", get_player()) 1051 | end) 1052 | 1053 | register_cheat_button("[spell refresh]", function() 1054 | GameRegenItemActionsInPlayer(get_player()) 1055 | end) 1056 | 1057 | register_cheat_button("[full heal]", function() quick_heal() end) 1058 | 1059 | register_cheat_button("[end fungal trip]", function() 1060 | EntityRemoveIngestionStatusEffect(get_player(), "TRIP" ) 1061 | end) 1062 | 1063 | register_cheat_button("[reset fungal shift timer]", function() 1064 | GlobalsSetValue("fungal_shift_last_frame", "-1000000") 1065 | end) 1066 | 1067 | register_cheat_button(function() 1068 | return "[" .. ((tourist_mode_on and "disable") or "enable") .. " tourist mode]" 1069 | end, toggle_tourist_mode) 1070 | 1071 | register_cheat_button("[spawn orbs]", function() 1072 | local x, y = get_player_pos() 1073 | for i = 0, 13 do 1074 | EntityLoad(("data/entities/items/orbs/orb_%02d.xml"):format(i), x+(i*15), y - (i*5)) 1075 | end 1076 | end) 1077 | 1078 | if _keyboard_present then 1079 | register_cheat_button("[open console]", function() 1080 | open_console() 1081 | enter_panel(console_panel) 1082 | end) 1083 | end 1084 | 1085 | enter_panel(menu_panel) 1086 | 1087 | -- widgets 1088 | local function StatsWidget(dispname, keyname, extra_pad) 1089 | local width = math.ceil(#dispname * 0.9) + (extra_pad or 3) 1090 | return { 1091 | text = function() 1092 | return ("%s: %s"):format(dispname, StatsGetValue(keyname) or "?") 1093 | end, 1094 | on_click = function() 1095 | goto_subpanel(info_panel) 1096 | end, 1097 | width = width 1098 | } 1099 | end 1100 | 1101 | register_widget("playtime", StatsWidget("Playtime", "playtime_str", 6)) 1102 | register_widget("visited", StatsWidget("Visited", "places_visited")) 1103 | register_widget("gold", StatsWidget("Gold", "gold_all")) 1104 | register_widget("hearts", StatsWidget("Hearts", "heart_containers")) 1105 | register_widget("items", StatsWidget("Items", "items")) 1106 | register_widget("projectiles", StatsWidget("Shot", "projectiles_shot", 3)) 1107 | register_widget("kicks", StatsWidget("Kicked", "kicks")) 1108 | register_widget("kills", StatsWidget("Kills", "enemies_killed")) 1109 | register_widget("damage", StatsWidget("Damage taken", "damage_taken")) 1110 | register_widget("frame", { 1111 | text = function() 1112 | return ("Frame: %08d"):format(GameGetFrameNum()) 1113 | end, 1114 | on_click = function() 1115 | goto_subpanel(info_panel) 1116 | end, 1117 | width = 16 1118 | }) 1119 | 1120 | register_widget("position", { 1121 | text = function() 1122 | local x, y = get_player_pos() 1123 | return ("X: %d, Y: %d"):format(x, y) 1124 | end, 1125 | on_click = function() 1126 | goto_subpanel(info_panel) 1127 | end, 1128 | width = 15 1129 | }) 1130 | 1131 | local localize_alchemy = false 1132 | 1133 | for _, recipe in ipairs{"LC", "AP"} do 1134 | local maxwidth = math.max( 1135 | #(alchemy_combos[recipe][true]), 1136 | #(alchemy_combos[recipe][false]) 1137 | ) 1138 | 1139 | register_widget(recipe, { 1140 | text = function() 1141 | return ("%s: %s"):format(recipe, alchemy_combos[recipe][localize_alchemy]) 1142 | end, 1143 | on_click = function() 1144 | localize_alchemy = not localize_alchemy 1145 | end, 1146 | width = math.ceil(maxwidth * 0.75) 1147 | }) 1148 | end 1149 | 1150 | function _cheat_gui_main() 1151 | if gui ~= nil then 1152 | GuiStartFrame( gui ) 1153 | end 1154 | 1155 | if _gui_frame_function ~= nil then 1156 | reset_id() 1157 | handle_typing() 1158 | local happy, errstr = pcall(_gui_frame_function) 1159 | if not happy then 1160 | print("Gui error: " .. errstr) 1161 | GamePrint("cheatgui err: " .. errstr) 1162 | if console_connected then 1163 | send_all_consoles(errstr .. ":" .. debug.traceback()) 1164 | end 1165 | hide_gui() 1166 | end 1167 | end 1168 | 1169 | wake_up_waiting_threads(1) -- from coroutines.lua 1170 | if console_connected and _socket_update then _socket_update() end 1171 | end 1172 | 1173 | hide_gui() -------------------------------------------------------------------------------- /data/hax/console.lua: -------------------------------------------------------------------------------- 1 | dofile_once("data/hax/lib/pollnet.lua") 2 | dofile_once("data/scripts/lib/coroutines.lua") 3 | 4 | -- this empty table is used as a special value that will suppress 5 | -- printing any kind of "RES>" value (normally "[no value]" would print) 6 | local UNPRINTABLE_RESULT = {} 7 | 8 | -- (from http://lua-users.org/wiki/SplitJoin) 9 | local strfind = string.find 10 | local tinsert = table.insert 11 | local strsub = string.sub 12 | local function strsplit(text, delimiter) 13 | local list = {} 14 | local pos = 1 15 | if strfind("", delimiter, 1) then -- this would result in endless loops 16 | error("Delimiter matches empty string!") 17 | end 18 | while 1 do 19 | local first, last = strfind(text, delimiter, pos) 20 | if first then -- found? 21 | tinsert(list, strsub(text, pos, first-1)) 22 | pos = last+1 23 | else 24 | tinsert(list, strsub(text, pos)) 25 | break 26 | end 27 | end 28 | return list 29 | end 30 | 31 | local function is_localhost(addr) 32 | local parts = strsplit(addr, ":") 33 | return parts[1] == "127.0.0.1" -- IPV6? 34 | end 35 | 36 | local function reload_utils(console_env) 37 | local env_utils, err = loadfile("data/hax/utils.lua") 38 | if type(env_utils) ~= "function" then 39 | console_env.print("Error loading utils: " .. tostring(err)) 40 | return 41 | end 42 | setfenv(env_utils, console_env) 43 | local happy, err = pcall(env_utils) 44 | if not happy then 45 | console_env.print("Error executing utils: " .. tostring(err)) 46 | end 47 | console_env.print("Utils loaded.") 48 | end 49 | 50 | local _help_info = nil 51 | local function reload_help(fn) 52 | fn = fn or "tools_modding/lua_api_documentation.txt" 53 | local f, err = io.open(fn) 54 | if not f then error("Couldn't open " .. fn) end 55 | local res = f:read("*a") 56 | f:close() 57 | if not res then error("Couldn't read " .. fn) end 58 | _help_info = {} 59 | res = res:gsub("\r", "") -- get rid of horrible carriage returns 60 | local lines = strsplit(res, "\n") 61 | for _, line in ipairs(lines) do 62 | local paren_idx = line:find("%(") 63 | if paren_idx then 64 | local funcname = line:sub(1, paren_idx-1) 65 | _help_info[funcname] = line 66 | end 67 | end 68 | end 69 | 70 | local function help_str(funcname) 71 | if not _help_info then reload_help() end 72 | return _help_info[funcname] 73 | end 74 | 75 | local function _strinfo(v) 76 | if v == nil then return "nil" end 77 | local vtype = type(v) 78 | if vtype == "number" then 79 | return ("%0.4f"):format(v) 80 | elseif vtype == "string" then 81 | return '"' .. v .. '"' 82 | elseif vtype == "boolean" then 83 | return tostring(v) 84 | else 85 | return ("[%s] %s"):format(vtype, tostring(v)) 86 | end 87 | end 88 | 89 | local function strinfo(...) 90 | local frags = {} 91 | local nargs = select('#', ...) 92 | if nargs == 0 then 93 | return "[no value]" 94 | end 95 | if nargs == 1 and select(1, ...) == UNPRINTABLE_RESULT then 96 | return UNPRINTABLE_RESULT 97 | end 98 | for idx = 1, nargs do 99 | frags[idx] = _strinfo(select(idx, ...)) 100 | end 101 | return table.concat(frags, ", ") 102 | end 103 | 104 | local function make_complete(console_env) 105 | return function(s) 106 | local opts = {} 107 | 108 | local parts = strsplit(s, "%.") -- strsplit takes a pattern, so have to escape "." 109 | local cur = console_env 110 | local prefix = "" 111 | for idx = 1, (#parts) - 1 do 112 | cur = cur[parts[idx]] 113 | if not cur then return UNPRINTABLE_RESULT end 114 | prefix = prefix .. parts[idx] .. "." 115 | end 116 | if type(cur) ~= "table" then return UNPRINTABLE_RESULT end 117 | local lastpart = parts[#parts] 118 | if not lastpart then return UNPRINTABLE_RESULT end 119 | for k, _ in pairs(cur) do 120 | if k:find(lastpart) == 1 then 121 | table.insert(opts, k) 122 | end 123 | end 124 | if #opts > 0 then 125 | table.sort(opts) 126 | console_env.send("COM>" .. prefix .. " " .. table.concat(opts, ",")) 127 | end 128 | return UNPRINTABLE_RESULT 129 | end 130 | end 131 | 132 | local function make_console_env(client) 133 | local console_env = {} 134 | for k, v in pairs(getfenv()) do 135 | console_env[k] = v 136 | end 137 | for k, v in pairs(cheatgui_stash) do 138 | if not console_env[k] then console_env[k] = v end 139 | end 140 | 141 | function console_env.print(...) 142 | local msg = table.concat({...}, " ") 143 | client:send("GAME> " .. msg) 144 | return UNPRINTABLE_RESULT 145 | end 146 | 147 | function console_env.err_print(...) 148 | local msg = table.concat({...}, " ") 149 | client:send("ERR> " .. msg) 150 | return UNPRINTABLE_RESULT 151 | end 152 | 153 | function console_env.send(msg) 154 | client:send(msg) 155 | end 156 | 157 | function console_env.print_table(t) 158 | local s = {} 159 | for k, v in pairs(t) do 160 | table.insert(s, k .. ": " .. type(v)) 161 | end 162 | console_env.print(table.concat(s, "\n")) 163 | end 164 | 165 | function console_env.log(...) 166 | local msg = table.concat({...}, " ") 167 | print(client.addr .. ": " .. msg) 168 | end 169 | 170 | function console_env.info(...) 171 | console_env.print(strinfo(...)) 172 | return UNPRINTABLE_RESULT 173 | end 174 | 175 | function console_env.dofile(fn) 176 | local s = loadfile(fn) 177 | if type(s) == 'string' then 178 | -- work around Noita's broken loadfile that returns error 179 | -- message as first argument rather than as second 180 | error(fn .. ": " .. s) 181 | end 182 | setfenv(s, console_env) 183 | return s() 184 | end 185 | 186 | function console_env.help(funcname) 187 | console_env.send("HELP> " .. (help_str(funcname) or (funcname .. "-> [no help available]"))) 188 | return UNPRINTABLE_RESULT 189 | end 190 | 191 | -- Hmm maybe it's just better to use regular async for this? 192 | --[[ 193 | console_env._persistent_funcs = {} 194 | 195 | function console_env.add_persistent_func(name, f) 196 | console_env._persistent_funcs[name] = f 197 | end 198 | 199 | function console_env.remove_persistent_func(name) 200 | console_env._persistent_funcs[name] = nil 201 | end 202 | 203 | function console_env.run_persistent_funcs() 204 | for fname, f in pairs(console_env._persistent_funcs) do 205 | local happy, err = pcall(f, fname) 206 | if not happy then console_env.err_print(err or "?") end 207 | end 208 | end 209 | ]] 210 | 211 | console_env.complete = make_complete(console_env) 212 | console_env.add_persistent_func = add_persistent_func 213 | console_env.set_persistent_func = add_persistent_func -- alias 214 | console_env.remove_persistent_func = remove_persistent_func 215 | console_env.strinfo = strinfo 216 | console_env.help_str = help_str 217 | console_env.UNPRINTABLE_RESULT = UNPRINTABLE_RESULT 218 | 219 | reload_utils(console_env) 220 | 221 | return console_env 222 | end 223 | 224 | local function _collect(happy, ...) 225 | if happy then 226 | return happy, strinfo(...) 227 | else 228 | return happy, ... 229 | end 230 | end 231 | 232 | local SCRATCH_SIZE = 1000000 -- might as well have a safe meg of space 233 | local ws_server_socket = nil 234 | local http_server = nil 235 | local ws_clients = {} 236 | 237 | local TOKEN_FN = "mods/cheatgui/token.json" 238 | local TOKEN_EXPIRATION = 6*3600 -- tokens expire after six hours 239 | 240 | local function read_raw_file(filename) 241 | local f, err = io.open(filename) 242 | if not f then return nil end 243 | local res = f:read("*a") 244 | f:close() 245 | return res 246 | end 247 | 248 | local function write_raw_file(filename, data) 249 | local f, err = io.open(filename, "w") 250 | if not f then error("Couldn't write " .. filename .. ": " .. err) end 251 | f:write(data) 252 | f:close() 253 | end 254 | 255 | local auth_token = nil 256 | local function generate_token() 257 | print("Cheatgui webconsole: generating new token.") 258 | auth_token = lib_pollnet.nanoid() 259 | write_raw_file(TOKEN_FN, JSON:encode_pretty{ 260 | token = auth_token, 261 | expiration = os.time() + TOKEN_EXPIRATION 262 | }) 263 | return auth_token 264 | end 265 | 266 | local function get_token() 267 | if not auth_token then 268 | if not JSON then dofile_once("data/hax/lib/json.lua") end 269 | local tdata = read_raw_file(TOKEN_FN) 270 | if tdata then 271 | tdata = JSON:decode(tdata) 272 | print("Got existing token: " .. tdata.token .. " -- " .. tdata.expiration) 273 | end 274 | if tdata and tdata.expiration and (tdata.expiration > os.time()) then 275 | auth_token = tdata.token 276 | else 277 | if tdata then 278 | print("Token expired? " .. tdata.expiration .. " vs. " .. os.time()) 279 | else 280 | print("No token; generating new.") 281 | end 282 | auth_token = generate_token() 283 | end 284 | end 285 | return auth_token 286 | end 287 | 288 | local function close_client(client) 289 | if client.sock then 290 | client.sock:close() 291 | client.sock = nil 292 | end 293 | ws_clients[client.addr] = nil 294 | end 295 | 296 | local function client_send(client, msg) 297 | if client.sock then 298 | client.stat_out = (client.stat_out or 0) + 1 299 | client.sock:send(msg) 300 | else 301 | client:close() 302 | end 303 | end 304 | 305 | local function on_new_client(sock, addr) 306 | print("New client: " .. addr) 307 | if ws_clients[addr] then ws_clients[addr].sock:close() end 308 | local new_client = {addr = addr, sock = sock, authorized = false, close = close_client, send = client_send, stat_in=0, stat_out=0} 309 | new_client.console_env = make_console_env(new_client) 310 | ws_clients[addr] = new_client 311 | end 312 | 313 | function listen_console_connections() 314 | lib_pollnet.link() 315 | if not ws_server_socket then 316 | ws_server_socket = lib_pollnet.listen_ws("127.0.0.1:9777", SCRATCH_SIZE) 317 | ws_server_socket:on_connection(on_new_client) 318 | http_server = lib_pollnet.serve_http("127.0.0.1:8777", "mods/cheatgui/www") 319 | end 320 | return get_token() 321 | end 322 | 323 | function get_console_connections() 324 | return ws_clients 325 | end 326 | 327 | function close_console_connections() 328 | for _, sock in pairs(ws_clients) do sock:close() end 329 | ws_clients = {} 330 | if ws_server_socket then ws_server_socket:close() end 331 | ws_server_socket = nil 332 | if http_server then http_server:close() end 333 | http_server = nil 334 | end 335 | 336 | function send_all_consoles(msg) 337 | for _, sock in pairs(ws_clients) do 338 | sock:send("ERR>" .. msg) 339 | end 340 | end 341 | 342 | local function check_authorization(client, msg) 343 | if not is_localhost(client.addr) then 344 | client.sock:send("SYS> UNAUTHORIZED: NOT LOCALHOST!") 345 | client.sock:close() 346 | client.sock = nil 347 | return 348 | end 349 | 350 | if msg:find(get_token()) then 351 | client.authorized = true 352 | client.sock:send("SYS> AUTHORIZED") 353 | GamePrint("Accepted console connection: " .. client.addr) 354 | else 355 | client.sock:send("SYS> UNAUTHORIZED: INVALID TOKEN") 356 | client.sock:close() 357 | client.sock = nil 358 | end 359 | end 360 | 361 | local function _handle_client_message(client, msg) 362 | if not client.authorized then 363 | return check_authorization(client, msg) 364 | end 365 | 366 | client.stat_in = (client.stat_in or 0) + 1 367 | local f, err = nil, nil 368 | if not msg:find("\n") then 369 | -- if this is a single line, try putting "return " in front 370 | -- (convenience to allow us to inspect values) 371 | f, err = loadstring("return " .. msg) 372 | end 373 | if not f then -- multiline, or not an expression 374 | f, err = loadstring(msg) 375 | if not f then 376 | client:send("ERR> Parse error: " .. tostring(err)) 377 | return 378 | end 379 | end 380 | setfenv(f, client.console_env) 381 | local happy, retval = _collect(pcall(f)) 382 | if happy then 383 | if retval ~= UNPRINTABLE_RESULT then 384 | client:send("RES> " .. tostring(retval)) 385 | end 386 | else 387 | client:send("ERR> " .. tostring(retval)) 388 | end 389 | end 390 | 391 | local count = 0 392 | function _socket_update() 393 | if not ws_server_socket then return end 394 | local happy, msg = ws_server_socket:poll() 395 | if not happy then 396 | print("Main WS server closed?") 397 | close_console_connections() 398 | return 399 | end 400 | 401 | for addr, client in pairs(ws_clients) do 402 | if client.sock then 403 | local happy, msg = client.sock:poll() 404 | if not happy then 405 | print("Sock error: " .. tostring(msg)) 406 | client.sock:close() 407 | client.sock = nil 408 | ws_clients[addr] = nil 409 | elseif msg then 410 | _handle_client_message(client, msg) 411 | end 412 | else 413 | ws_clients[addr] = nil 414 | end 415 | end 416 | 417 | if (count % 60 == 0) and http_server then 418 | local happy, errmsg = http_server:poll() 419 | if not happy then 420 | print("HTTP server closed: " .. tostring(errmsg)) 421 | http_server:close() 422 | http_server = nil 423 | end 424 | end 425 | 426 | count = count + 1 427 | end -------------------------------------------------------------------------------- /data/hax/fungal.lua: -------------------------------------------------------------------------------- 1 | local fungal_env = nil 2 | local _predict_transform = nil 3 | 4 | local STASH = cheatgui_stash 5 | 6 | if not loadstring then 7 | local VIRT_PATH = "mods/cheatgui/_fake_fungal_shift.lua" 8 | function loadstring(buf) 9 | STASH.ModTextFileSetContent(VIRT_PATH, buf) 10 | return loadfile(VIRT_PATH) 11 | end 12 | end 13 | 14 | STASH.loadstring = loadstring 15 | 16 | local function _dofile(fn) 17 | local src = STASH.ModTextFileGetContent(fn) 18 | if not src then return nil end 19 | src = STASH.loadstring(src) 20 | if not src then return nil end 21 | setfenv(src, fungal_env) 22 | return src() 23 | end 24 | 25 | local _done_files = {} 26 | local function _dofile_once(fn) 27 | if _done_files[fn] then return _done_files[fn] end 28 | _done_files[fn] = _dofile(fn) 29 | return _done_files[fn] 30 | end 31 | 32 | local MOCKED_FUNCTIONS = { 33 | "GlobalsSetValue", 34 | "GameCreateParticle", 35 | "GamePrintImportant", 36 | "GamePrint", 37 | "EntityCreateNew", 38 | "EntityAddComponent", 39 | "EntityAddChild", 40 | "GameTriggerMusicFadeOutAndDequeueAll", 41 | "GameTriggerMusicEvent", 42 | "EntityLoad", 43 | "EntityRemoveIngestionStatusEffect", 44 | "print", -- stop fungal_shift from spamming the log!!! 45 | } 46 | 47 | local _fungal_iteration = 0 48 | local function load_fungal_env() 49 | fungal_env = {} 50 | 51 | for k, v in pairs(cheatgui_stash) do fungal_env[k] = v end 52 | fungal_env.dofile = _dofile 53 | fungal_env.dofile_once = _dofile_once 54 | setfenv(_dofile, fungal_env) 55 | setfenv(_dofile_once, fungal_env) 56 | 57 | for _, v in ipairs(MOCKED_FUNCTIONS) do 58 | fungal_env[v] = function(...) 59 | -- haha do nothing 60 | end 61 | end 62 | 63 | fungal_env.GlobalsGetValue = function(v, ...) 64 | if v == "fungal_shift_last_frame" then 65 | -- force it to always give an answer 66 | return -10000 67 | elseif v == "fungal_shift_iteration" then 68 | -- allow us to manipulate the iteration 69 | return tostring(_fungal_iteration) 70 | else 71 | return GlobalsGetValue(v, ...) 72 | end 73 | end 74 | 75 | local fungal_script_src = cheatgui_stash.ModTextFileGetContent("data/scripts/magic/fungal_shift.lua") 76 | local fscript = loadstring(fungal_script_src) 77 | setfenv(fscript, fungal_env) 78 | fscript() 79 | end 80 | 81 | local function get_player() 82 | return (EntityGetWithTag( "player_unit" ) or {})[1] 83 | end 84 | 85 | local function get_player_pos() 86 | local player = get_player() 87 | if not player then return 0, 0 end 88 | return EntityGetTransform(player) 89 | end 90 | 91 | local function predict_transform(iter_offset) 92 | _fungal_iteration = tonumber(GlobalsGetValue("fungal_shift_iteration", "0")) + iter_offset 93 | local _from, _to = nil, nil 94 | fungal_env.ConvertMaterialEverywhere = function(from_material, to_material) 95 | _from = CellFactory_GetUIName(from_material) --:gsub("$", "") 96 | _to = CellFactory_GetUIName(to_material) --:gsub("$", "") 97 | end 98 | pcall(fungal_env.fungal_shift, get_player(), get_player_pos()) 99 | return _from, _to 100 | end 101 | 102 | local function to_material_int_type(v) 103 | if type(v) ~= "number" then 104 | v = CellFactory_GetType(v) 105 | end 106 | return v 107 | end 108 | 109 | function fungal_force_convert(from, to) 110 | ConvertMaterialEverywhere( 111 | to_material_int_type(from), 112 | to_material_int_type(to) 113 | ) 114 | end 115 | 116 | function fungal_predict_transform(iter_offset) 117 | if not fungal_env then load_fungal_env() end 118 | if not fungal_env then return nil end 119 | return predict_transform(iter_offset) 120 | end -------------------------------------------------------------------------------- /data/hax/gun_builder.lua: -------------------------------------------------------------------------------- 1 | dofile("data/scripts/gun/procedural/gun_procedural.lua") 2 | 3 | function build_gun(x, y, gun) 4 | local entity_id = EntityLoad("data/hax/wand_empty.xml", x, y) 5 | local ability_comp = EntityGetFirstComponent( entity_id, "AbilityComponent" ) 6 | 7 | gun.cost = gun.cost or 0 8 | gun.deck_capacity = gun.deck_capacity or 5 9 | gun.actions_per_round = gun.actions_per_round or 1 10 | gun.reload_time = gun.reload_time or 30 11 | gun.shuffle_deck_when_empty = gun.shuffle_deck_when_empty or 0 12 | gun.fire_rate_wait = gun.fire_rate_wait or 30 13 | gun.spread_degrees = gun.spread_degrees or 0 14 | gun.speed_multiplier = gun.speed_multiplier or 1 15 | gun.prob_unshuffle = gun.prob_unshuffle or 0.1 16 | gun.prob_draw_many = gun.prob_draw_many or 0.15 17 | gun.mana_charge_speed = gun.mana_charge_speed or 10000 18 | gun.mana_max = gun.mana_max or 10000 19 | gun.force_unshuffle = gun.force_unshuffle or 1 20 | 21 | local name = "HAXXXX" --ComponentGetValue( ability_comp, "ui_name" ) 22 | 23 | ComponentSetValue( ability_comp, "ui_name", name ) 24 | ComponentObjectSetValue( ability_comp, "gun_config", "actions_per_round", gun["actions_per_round"] ) 25 | ComponentObjectSetValue( ability_comp, "gun_config", "reload_time", gun["reload_time"] ) 26 | ComponentObjectSetValue( ability_comp, "gun_config", "deck_capacity", gun["deck_capacity"] ) 27 | ComponentObjectSetValue( ability_comp, "gun_config", "shuffle_deck_when_empty", gun["shuffle_deck_when_empty"] ) 28 | ComponentObjectSetValue( ability_comp, "gunaction_config", "fire_rate_wait", gun["fire_rate_wait"] ) 29 | ComponentObjectSetValue( ability_comp, "gunaction_config", "spread_degrees", gun["spread_degrees"] ) 30 | ComponentObjectSetValue( ability_comp, "gunaction_config", "speed_multiplier", gun["speed_multiplier"] ) 31 | ComponentSetValue( ability_comp, "mana_charge_speed", gun["mana_charge_speed"]) 32 | ComponentSetValue( ability_comp, "mana_max", gun["mana_max"]) 33 | ComponentSetValue( ability_comp, "mana", gun["mana_max"]) 34 | ComponentSetValue( ability_comp, "item_recoil_recovery_speed", 15.0 ) -- TODO: implement logic for setting this 35 | 36 | local always_casts = gun["always_casts"] or {gun["always_cast"]} or {} 37 | for _, spell_id in ipairs(always_casts) do 38 | AddGunActionPermanent( entity_id, spell_id ) 39 | end 40 | 41 | 42 | local wand = GetWand( gun ) 43 | SetWandSprite( entity_id, ability_comp, wand.file, wand.grip_x, wand.grip_y, (wand.tip_x - wand.grip_x), (wand.tip_y - wand.grip_y) ) 44 | end -------------------------------------------------------------------------------- /data/hax/lib/pollnet.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | pollnet bindings for luajit 3 | 4 | example usage to read twitch chat: 5 | local pollnet = require("pollnet") 6 | local async = require("async") -- assuming you have some kind of async 7 | async.run(function() 8 | local url = "wss://irc-ws.chat.twitch.tv:443" 9 | local sock = pollnet.open_ws(url) 10 | sock:send("PASS doesntmatter") 11 | -- special nick for anon read-only access on twitch 12 | local anon_user_name = "justinfan" .. math.random(1, 100000) 13 | local target_channel = "your_channel_name_here" 14 | sock:send("NICK " .. anon_user_name) 15 | sock:send("JOIN #" .. target_channel) 16 | 17 | while sock:poll() do 18 | local msg = sock:last_message() 19 | if msg then 20 | if msg == "PING :tmi.twitch.tv" then 21 | sock:send("PONG :tmi.twitch.tv") 22 | end 23 | print(msg) 24 | end 25 | async.await_frames(1) 26 | end 27 | print("Socket closed: ", sock:last_message()) 28 | end) 29 | ]] 30 | 31 | local ffi = require("ffi") 32 | ffi.cdef[[ 33 | struct pnctx* pollnet_init(); 34 | struct pnctx* pollnet_get_or_init_static(); 35 | void pollnet_shutdown(struct pnctx* ctx); 36 | unsigned int pollnet_open_ws(struct pnctx* ctx, const char* url); 37 | void pollnet_close(struct pnctx* ctx, unsigned int handle); 38 | void pollnet_close_all(struct pnctx* ctx); 39 | void pollnet_send(struct pnctx* ctx, unsigned int handle, const char* msg); 40 | unsigned int pollnet_update(struct pnctx* ctx, unsigned int handle); 41 | int pollnet_get(struct pnctx* ctx, unsigned int handle, char* dest, unsigned int dest_size); 42 | int pollnet_get_error(struct pnctx* ctx, unsigned int handle, char* dest, unsigned int dest_size); 43 | unsigned int pollnet_get_connected_client_handle(struct pnctx* ctx, unsigned int handle); 44 | unsigned int pollnet_listen_ws(struct pnctx* ctx, const char* addr); 45 | unsigned int pollnet_serve_static_http(struct pnctx* ctx, const char* addr, const char* serve_dir); 46 | unsigned int pollnet_serve_http(struct pnctx* ctx, const char* addr); 47 | void pollnet_add_virtual_file(struct pnctx* ctx, unsigned int handle, const char* filename, const char* filedata, unsigned int filesize); 48 | void pollnet_remove_virtual_file(struct pnctx* ctx, unsigned int handle, const char* filename); 49 | int pollnet_get_nanoid(char* dest, unsigned int dest_size); 50 | ]] 51 | 52 | local POLLNET_RESULT_CODES = { 53 | [0] = "invalid_handle", 54 | [1] = "closed", 55 | [2] = "opening", 56 | [3] = "nodata", 57 | [4] = "hasdata", 58 | [5] = "error", 59 | [6] = "newclient" 60 | } 61 | 62 | local LIB_PATH = "mods/cheatgui/bin/pollnet.dll" 63 | 64 | local pollnet = nil 65 | local _ctx = nil 66 | 67 | local function link_pollnet(return_error) 68 | if pollnet then return end 69 | local happy, res = pcall(ffi.load, LIB_PATH) 70 | local err_msg = "Pollnet DLL missing or corrupt: " .. LIB_PATH 71 | if happy then 72 | pollnet = res 73 | elseif return_error then 74 | return err_msg .. ": " .. res 75 | else 76 | error(err_msg .. ": " .. res) 77 | end 78 | end 79 | 80 | local function init_ctx() 81 | if _ctx then return end 82 | if not pollnet then link_pollnet() end 83 | _ctx = ffi.gc(pollnet.pollnet_init(), pollnet.pollnet_shutdown) 84 | assert(_ctx ~= nil) 85 | end 86 | 87 | local function init_ctx_hack_static() 88 | if _ctx then return end 89 | _ctx = pollnet.pollnet_get_or_init_static() 90 | assert(_ctx ~= nil) 91 | pollnet.pollnet_close_all(_ctx) 92 | end 93 | 94 | local function shutdown_ctx() 95 | if not _ctx then return end 96 | pollnet.pollnet_shutdown(ffi.gc(_ctx, nil)) 97 | _ctx = nil 98 | end 99 | 100 | local socket_mt = {} 101 | local function Socket() 102 | return setmetatable({}, {__index = socket_mt}) 103 | end 104 | 105 | function socket_mt:_open(scratch_size, opener, ...) 106 | init_ctx() 107 | if not _ctx then return end 108 | if self._socket then self:close() end 109 | if not scratch_size then scratch_size = 64000 end 110 | if type(opener) == "number" then 111 | self._socket = opener 112 | else 113 | self._socket = opener(_ctx, ...) 114 | end 115 | self._scratch = ffi.new("int8_t[?]", scratch_size) 116 | self._scratch_size = scratch_size 117 | self._status = "unpolled" 118 | return self 119 | end 120 | 121 | function socket_mt:open_ws(url, scratch_size) 122 | return self:_open(scratch_size, pollnet.pollnet_open_ws, url) 123 | end 124 | 125 | function socket_mt:serve_http(addr, dir, scratch_size) 126 | self.is_http_server = true 127 | if dir and dir ~= "" then 128 | return self:_open(scratch_size, pollnet.pollnet_serve_static_http, addr, dir) 129 | else 130 | return self:_open(scratch_size, pollnet.pollnet_serve_http, addr) 131 | end 132 | end 133 | 134 | function socket_mt:add_virtual_file(filename, filedata) 135 | assert(filedata) 136 | local dsize = #filedata 137 | pollnet.pollnet_add_virtual_file(_ctx, self._socket, filename, filedata, dsize) 138 | end 139 | 140 | function socket_mt:remove_virtual_file(filename) 141 | pollnet.pollnet_remove_virtual_file(_ctx, self._socket, filename) 142 | end 143 | 144 | function socket_mt:listen_ws(addr, scratch_size) 145 | return self:_open(scratch_size, pollnet.pollnet_listen_ws, addr) 146 | end 147 | 148 | function socket_mt:on_connection(f) 149 | self._on_connection = f 150 | return self 151 | end 152 | 153 | function socket_mt:_get_message() 154 | local msg_size = pollnet.pollnet_get(_ctx, self._socket, self._scratch, self._scratch_size) 155 | if msg_size > 0 then 156 | return ffi.string(self._scratch, msg_size) 157 | else 158 | return nil 159 | end 160 | end 161 | 162 | function socket_mt:poll() 163 | if not self._socket then 164 | self._status = "invalid" 165 | return false, "invalid" 166 | end 167 | local res = POLLNET_RESULT_CODES[pollnet.pollnet_update(_ctx, self._socket)] or "error" 168 | self._status = res 169 | self._last_message = nil 170 | if res == "hasdata" then 171 | self._status = "open" 172 | self._last_message = self:_get_message() 173 | return true, self._last_message 174 | elseif res == "nodata" then 175 | self._status = "open" 176 | return true 177 | elseif res == "opening" then 178 | self._status = "opening" 179 | return true 180 | elseif res == "error" then 181 | self._last_message = self:error_msg() 182 | return false, self._last_message 183 | elseif res == "closed" then 184 | return false, "closed" 185 | elseif res == "newclient" then 186 | local client_addr = self:_get_message() 187 | local client_handle = pollnet.pollnet_get_connected_client_handle(_ctx, self._socket) 188 | assert(client_handle > 0) 189 | local client_sock = Socket():_open(self._scratch_size, client_handle) 190 | client_sock.parent = self 191 | client_sock.remote_addr = client_addr 192 | if self._on_connection then 193 | self._on_connection(client_sock, client_addr) 194 | else 195 | print("No connection handler! All incoming connections will be closed!") 196 | client_sock:close() 197 | end 198 | return true 199 | end 200 | end 201 | 202 | function socket_mt:last_message() 203 | return self._last_message 204 | end 205 | function socket_mt:status() 206 | return self._status 207 | end 208 | function socket_mt:send(msg) 209 | assert(self._socket) 210 | pollnet.pollnet_send(_ctx, self._socket, msg) 211 | end 212 | function socket_mt:close() 213 | assert(self._socket) 214 | pollnet.pollnet_close(_ctx, self._socket) 215 | self._socket = nil 216 | end 217 | function socket_mt:error_msg() 218 | if not self._socket then return "No socket!" end 219 | local msg_size = pollnet.pollnet_get_error(_ctx, self._socket, self._scratch, self._scratch_size) 220 | if msg_size > 0 then 221 | local smsg = ffi.string(self._scratch, msg_size) 222 | return smsg 223 | else 224 | return nil 225 | end 226 | end 227 | 228 | local function open_ws(url, scratch_size) 229 | return Socket():open_ws(url, scratch_size) 230 | end 231 | 232 | local function listen_ws(addr, scratch_size) 233 | return Socket():listen_ws(addr, scratch_size) 234 | end 235 | 236 | local function serve_http(addr, dir, scratch_size) 237 | return Socket():serve_http(addr, dir, scratch_size) 238 | end 239 | 240 | local function get_nanoid() 241 | local _id_scratch = ffi.new("int8_t[?]", 128) 242 | local msg_size = pollnet.pollnet_get_nanoid(_id_scratch, 128) 243 | return ffi.string(_id_scratch, msg_size) 244 | end 245 | 246 | lib_pollnet = { 247 | init = init_ctx, 248 | link = link_pollnet, 249 | init_hack_static = init_ctx_hack_static, 250 | shutdown = shutdown_ctx, 251 | open_ws = open_ws, 252 | listen_ws = listen_ws, 253 | serve_http = serve_http, 254 | Socket = Socket, 255 | pollnet = pollnet, 256 | nanoid = get_nanoid, 257 | } -------------------------------------------------------------------------------- /data/hax/materials.lua: -------------------------------------------------------------------------------- 1 | materials_list = {} 2 | 3 | local get_name = function(mat) 4 | return mat 5 | end 6 | 7 | if not DebugGetIsDevBuild() then 8 | -- Dev build likes to complain about translations 9 | get_name = function(mat) 10 | local n = GameTextGet("$mat_" .. mat) 11 | if n and n ~= "" then return n else return "[" .. mat .. "]" end 12 | end 13 | end 14 | 15 | for _, category in ipairs{"Liquids", "Solids", "Sands", "Gases", "Fires"} do 16 | table.insert(materials_list, {"-- " .. category .. " --", "-- " .. category .. " --"}) 17 | local mats = getfenv()["CellFactory_GetAll" .. category]() 18 | print("Got " .. #mats .. " " .. category) 19 | table.sort(mats) 20 | for _, mat in ipairs(mats) do 21 | table.insert(materials_list, {mat, get_name(mat)}) 22 | end 23 | end 24 | 25 | -- local getters = { 26 | -- {"Fires", CellFactory_GetAllFires, 27 | -- CellFactory_GetAllGases, 28 | -- CellFactory_GetAllSolids, 29 | -- CellFactory_GetAllSands, 30 | -- CellFactory_GetAllLiquids 31 | -- } -------------------------------------------------------------------------------- /data/hax/spawnables.lua: -------------------------------------------------------------------------------- 1 | 2 | -- AUTOGENERATED! DO NOT EDIT DIRECTLY! 3 | -- RUN 'gen_spawnlist.py' TO REGENERATE! (requires unpacked data!) 4 | -- MANUALLY ADD special items to 'special_spawnables.lua'! 5 | spawn_list = { 6 | {path='data/entities/items/books/base_book.xml', name='$booktitle01', xml='base_book.xml'}, 7 | {path='data/entities/items/books/base_forged.xml', name='$booktitle01', xml='base_forged.xml'}, 8 | {path='data/entities/items/books/book_00.xml', name='$booktitle00', xml='book_00.xml'}, 9 | {path='data/entities/items/books/book_01.xml', name='$booktitle01', xml='book_01.xml'}, 10 | {path='data/entities/items/books/book_02.xml', name='$booktitle02', xml='book_02.xml'}, 11 | {path='data/entities/items/books/book_03.xml', name='$booktitle03', xml='book_03.xml'}, 12 | {path='data/entities/items/books/book_04.xml', name='$booktitle04', xml='book_04.xml'}, 13 | {path='data/entities/items/books/book_05.xml', name='$booktitle05', xml='book_05.xml'}, 14 | {path='data/entities/items/books/book_06.xml', name='$booktitle06', xml='book_06.xml'}, 15 | {path='data/entities/items/books/book_07.xml', name='$booktitle07', xml='book_07.xml'}, 16 | {path='data/entities/items/books/book_08.xml', name='$booktitle08', xml='book_08.xml'}, 17 | {path='data/entities/items/books/book_09.xml', name='$booktitle09', xml='book_09.xml'}, 18 | {path='data/entities/items/books/book_10.xml', name='$booktitle10', xml='book_10.xml'}, 19 | {path='data/entities/items/books/book_11.xml', name='$booktitle11', xml='book_11.xml'}, 20 | {path='data/entities/items/books/book_12.xml', name='$booktitle12', xml='book_12.xml'}, 21 | {path='data/entities/items/books/book_all_spells.xml', name='$booktitle_allspells', xml='book_all_spells.xml'}, 22 | {path='data/entities/items/books/book_bunker.xml', name='$booktitle_fisher', xml='book_bunker.xml'}, 23 | {path='data/entities/items/books/book_corpse.xml', name='$booktitle_corpse', xml='book_corpse.xml'}, 24 | {path='data/entities/items/books/book_diamond.xml', name='$item_book_diamond', xml='book_diamond.xml'}, 25 | {path='data/entities/items/books/book_essences.xml', name='$item_book_essences', xml='book_essences.xml'}, 26 | {path='data/entities/items/books/book_hint.xml', name='$item_book_hint', xml='book_hint.xml'}, 27 | {path='data/entities/items/books/book_mestari.xml', name='$booktitle_mestari', xml='book_mestari.xml'}, 28 | {path='data/entities/items/books/book_moon.xml', name='$item_book_moon', xml='book_moon.xml'}, 29 | {path='data/entities/items/books/book_music_a.xml', name='$item_book_music', xml='book_music_a.xml'}, 30 | {path='data/entities/items/books/book_music_b.xml', name='$item_book_music_b', xml='book_music_b.xml'}, 31 | {path='data/entities/items/books/book_music_c.xml', name='$item_book_music_c', xml='book_music_c.xml'}, 32 | {path='data/entities/items/books/book_robot.xml', name='$item_book_robot', xml='book_robot.xml'}, 33 | {path='data/entities/items/books/book_s_a.xml', name='$item_book_s_a', xml='book_s_a.xml'}, 34 | {path='data/entities/items/books/book_s_b.xml', name='$item_book_s_a', xml='book_s_b.xml'}, 35 | {path='data/entities/items/books/book_s_c.xml', name='$item_book_s_a', xml='book_s_c.xml'}, 36 | {path='data/entities/items/books/book_s_d.xml', name='$item_book_s_a', xml='book_s_d.xml'}, 37 | {path='data/entities/items/books/book_s_e.xml', name='$item_book_s_a', xml='book_s_e.xml'}, 38 | {path='data/entities/items/books/book_tree.xml', name='$booktitle_tree', xml='book_tree.xml'}, 39 | {path='data/entities/items/easter/beer_bottle.xml', name='$item_potion', xml='beer_bottle.xml'}, 40 | {path='data/entities/items/easter/druidsoccer_ball.xml', name='druidsoccer_ball.xml', xml='druidsoccer_ball.xml'}, 41 | {path='data/entities/items/easter/minit_watering.xml', name='Potion', xml='minit_watering.xml'}, 42 | {path='data/entities/items/flute.xml', name='$item_ocarina', xml='flute.xml'}, 43 | {path='data/entities/items/kantele.xml', name='$item_kantele', xml='kantele.xml'}, 44 | {path='data/entities/items/orbs/orb_00.xml', name='orb_00.xml', xml='orb_00.xml'}, 45 | {path='data/entities/items/orbs/orb_01.xml', name='orb_01.xml', xml='orb_01.xml'}, 46 | {path='data/entities/items/orbs/orb_02.xml', name='orb_02.xml', xml='orb_02.xml'}, 47 | {path='data/entities/items/orbs/orb_03.xml', name='orb_03.xml', xml='orb_03.xml'}, 48 | {path='data/entities/items/orbs/orb_04.xml', name='orb_04.xml', xml='orb_04.xml'}, 49 | {path='data/entities/items/orbs/orb_05.xml', name='orb_05.xml', xml='orb_05.xml'}, 50 | {path='data/entities/items/orbs/orb_06.xml', name='orb_06.xml', xml='orb_06.xml'}, 51 | {path='data/entities/items/orbs/orb_07.xml', name='orb_07.xml', xml='orb_07.xml'}, 52 | {path='data/entities/items/orbs/orb_08.xml', name='orb_08.xml', xml='orb_08.xml'}, 53 | {path='data/entities/items/orbs/orb_09.xml', name='orb_09.xml', xml='orb_09.xml'}, 54 | {path='data/entities/items/orbs/orb_10.xml', name='orb_10.xml', xml='orb_10.xml'}, 55 | {path='data/entities/items/orbs/orb_11.xml', name='orb_11.xml', xml='orb_11.xml'}, 56 | {path='data/entities/items/orbs/orb_13.xml', name='orb_13.xml', xml='orb_13.xml'}, 57 | {path='data/entities/items/orbs/orb_base.xml', name='$item_orb', xml='orb_base.xml'}, 58 | {path='data/entities/items/orbs/orb_particles_base.xml', name='orb_particles_base.xml', xml='orb_particles_base.xml'}, 59 | {path='data/entities/items/pickup/beamstone.xml', name='$item_mega_beam_stone', xml='beamstone.xml'}, 60 | {path='data/entities/items/pickup/bloodmoney_10.xml', name='$item_bloodmoney_10', xml='bloodmoney_10.xml'}, 61 | {path='data/entities/items/pickup/bloodmoney_1000.xml', name='$item_bloodmoney_1000', xml='bloodmoney_1000.xml'}, 62 | {path='data/entities/items/pickup/bloodmoney_10000.xml', name='$item_bloodmoney_10000', xml='bloodmoney_10000.xml'}, 63 | {path='data/entities/items/pickup/bloodmoney_200.xml', name='$item_bloodmoney_200', xml='bloodmoney_200.xml'}, 64 | {path='data/entities/items/pickup/bloodmoney_200000.xml', name='$item_bloodmoney_200000', xml='bloodmoney_200000.xml'}, 65 | {path='data/entities/items/pickup/bloodmoney_50.xml', name='$item_bloodmoney_50', xml='bloodmoney_50.xml'}, 66 | {path='data/entities/items/pickup/brimstone.xml', name='$item_brimstone', xml='brimstone.xml'}, 67 | {path='data/entities/items/pickup/broken_wand.xml', name='$item_broken_wand', xml='broken_wand.xml'}, 68 | {path='data/entities/items/pickup/cape.xml', name='Fire protection cape', xml='cape.xml'}, 69 | {path='data/entities/items/pickup/chest_leggy.xml', name='$item_chest_treasure', xml='chest_leggy.xml'}, 70 | {path='data/entities/items/pickup/chest_random.xml', name='$item_chest_treasure', xml='chest_random.xml'}, 71 | {path='data/entities/items/pickup/chest_random_super.xml', name='$item_chest_treasure', xml='chest_random_super.xml'}, 72 | {path='data/entities/items/pickup/egg_fire.xml', name='$item_egg_fire', xml='egg_fire.xml'}, 73 | {path='data/entities/items/pickup/egg_hollow.xml', name='$item_egg_hollow', xml='egg_hollow.xml'}, 74 | {path='data/entities/items/pickup/egg_monster.xml', name='$item_egg', xml='egg_monster.xml'}, 75 | {path='data/entities/items/pickup/egg_purple.xml', name='$item_egg_purple', xml='egg_purple.xml'}, 76 | {path='data/entities/items/pickup/egg_red.xml', name='$item_egg', xml='egg_red.xml'}, 77 | {path='data/entities/items/pickup/egg_slime.xml', name='$item_egg_slime', xml='egg_slime.xml'}, 78 | {path='data/entities/items/pickup/egg_spiders.xml', name='$item_egg_purple', xml='egg_spiders.xml'}, 79 | {path='data/entities/items/pickup/egg_worm.xml', name='$item_egg_worm', xml='egg_worm.xml'}, 80 | {path='data/entities/items/pickup/essence_air.xml', name='$item_essence_air', xml='essence_air.xml'}, 81 | {path='data/entities/items/pickup/essence_alcohol.xml', name='$item_essence_alcohol', xml='essence_alcohol.xml'}, 82 | {path='data/entities/items/pickup/essence_fire.xml', name='$item_essence_fire', xml='essence_fire.xml'}, 83 | {path='data/entities/items/pickup/essence_laser.xml', name='$item_essence_laser', xml='essence_laser.xml'}, 84 | {path='data/entities/items/pickup/essence_water.xml', name='$item_essence_water', xml='essence_water.xml'}, 85 | {path='data/entities/items/pickup/evil_eye.xml', name='$item_evil_eye', xml='evil_eye.xml'}, 86 | {path='data/entities/items/pickup/goldnugget.xml', name='$item_goldnugget_10', xml='goldnugget.xml'}, 87 | {path='data/entities/items/pickup/goldnugget_10.xml', name='$item_goldnugget_10', xml='goldnugget_10.xml'}, 88 | {path='data/entities/items/pickup/goldnugget_1000.xml', name='$item_goldnugget_1000', xml='goldnugget_1000.xml'}, 89 | {path='data/entities/items/pickup/goldnugget_10000.xml', name='$item_goldnugget_10000', xml='goldnugget_10000.xml'}, 90 | {path='data/entities/items/pickup/goldnugget_200.xml', name='$item_goldnugget_200', xml='goldnugget_200.xml'}, 91 | {path='data/entities/items/pickup/goldnugget_200000.xml', name='$item_goldnugget_200000', xml='goldnugget_200000.xml'}, 92 | {path='data/entities/items/pickup/goldnugget_50.xml', name='$item_goldnugget_50', xml='goldnugget_50.xml'}, 93 | {path='data/entities/items/pickup/gourd.xml', name='$item_gourd', xml='gourd.xml'}, 94 | {path='data/entities/items/pickup/greed_curse.xml', name='$item_essence_greed', xml='greed_curse.xml'}, 95 | {path='data/entities/items/pickup/heart.xml', name='$item_heart', xml='heart.xml'}, 96 | {path='data/entities/items/pickup/heart_better.xml', name='$item_heart_better', xml='heart_better.xml'}, 97 | {path='data/entities/items/pickup/heart_evil.xml', name='$item_heart', xml='heart_evil.xml'}, 98 | {path='data/entities/items/pickup/heart_fullhp.xml', name='$item_heart_fullhp', xml='heart_fullhp.xml'}, 99 | {path='data/entities/items/pickup/heart_fullhp_temple.xml', name='heart_fullhp_temple.xml', xml='heart_fullhp_temple.xml'}, 100 | {path='data/entities/items/pickup/jar.xml', name='$item_jar', xml='jar.xml'}, 101 | {path='data/entities/items/pickup/jar_of_urine.xml', name='jar_of_urine.xml', xml='jar_of_urine.xml'}, 102 | {path='data/entities/items/pickup/moon.xml', name='$item_moon', xml='moon.xml'}, 103 | {path='data/entities/items/pickup/musicstone.xml', name='$item_musicstone', xml='musicstone.xml'}, 104 | {path='data/entities/items/pickup/perk.xml', name='perk.xml', xml='perk.xml'}, 105 | {path='data/entities/items/pickup/perk_reroll.xml', name='$item_perk_reroll', xml='perk_reroll.xml'}, 106 | {path='data/entities/items/pickup/physics_die.xml', name='$item_die', xml='physics_die.xml'}, 107 | {path='data/entities/items/pickup/physics_gold_orb.xml', name='$item_gold_orb', xml='physics_gold_orb.xml'}, 108 | {path='data/entities/items/pickup/physics_gold_orb_greed.xml', name='$item_gold_orb_greed', xml='physics_gold_orb_greed.xml'}, 109 | {path='data/entities/items/pickup/physics_greed_die.xml', name='$item_greed_die', xml='physics_greed_die.xml'}, 110 | {path='data/entities/items/pickup/poopstone.xml', name='$item_kakka', xml='poopstone.xml'}, 111 | {path='data/entities/items/pickup/potion.xml', name='$item_potion', xml='potion.xml'}, 112 | {path='data/entities/items/pickup/potion_aggressive.xml', name='potion_aggressive.xml', xml='potion_aggressive.xml'}, 113 | {path='data/entities/items/pickup/potion_alcohol.xml', name='potion_alcohol.xml', xml='potion_alcohol.xml'}, 114 | {path='data/entities/items/pickup/potion_empty.xml', name='potion_empty.xml', xml='potion_empty.xml'}, 115 | {path='data/entities/items/pickup/potion_porridge.xml', name='potion_porridge.xml', xml='potion_porridge.xml'}, 116 | {path='data/entities/items/pickup/potion_random_material.xml', name='potion_random_material.xml', xml='potion_random_material.xml'}, 117 | {path='data/entities/items/pickup/potion_secret.xml', name='potion_secret.xml', xml='potion_secret.xml'}, 118 | {path='data/entities/items/pickup/potion_slime.xml', name='potion_slime.xml', xml='potion_slime.xml'}, 119 | {path='data/entities/items/pickup/potion_starting.xml', name='potion_starting.xml', xml='potion_starting.xml'}, 120 | {path='data/entities/items/pickup/potion_vomit.xml', name='potion_vomit.xml', xml='potion_vomit.xml'}, 121 | {path='data/entities/items/pickup/potion_water.xml', name='potion_water.xml', xml='potion_water.xml'}, 122 | {path='data/entities/items/pickup/powder_stash.xml', name='$item_powder_stash_3', xml='powder_stash.xml'}, 123 | {path='data/entities/items/pickup/random_card.xml', name='random_card.xml', xml='random_card.xml'}, 124 | {path='data/entities/items/pickup/runestones/runestone_base.xml', name='$item_runestone_slow', xml='runestone_base.xml'}, 125 | {path='data/entities/items/pickup/runestones/runestone_disc.xml', name='$item_runestone_disc', xml='runestone_disc.xml'}, 126 | {path='data/entities/items/pickup/runestones/runestone_fireball.xml', name='$item_runestone_fireball', xml='runestone_fireball.xml'}, 127 | {path='data/entities/items/pickup/runestones/runestone_laser.xml', name='$item_runestone_laser', xml='runestone_laser.xml'}, 128 | {path='data/entities/items/pickup/runestones/runestone_lava.xml', name='$item_runestone_lava', xml='runestone_lava.xml'}, 129 | {path='data/entities/items/pickup/runestones/runestone_metal.xml', name='$item_runestone_metal', xml='runestone_metal.xml'}, 130 | {path='data/entities/items/pickup/runestones/runestone_null.xml', name='$item_runestone_null', xml='runestone_null.xml'}, 131 | {path='data/entities/items/pickup/runestones/runestone_slow.xml', name='$item_runestone_slow', xml='runestone_slow.xml'}, 132 | {path='data/entities/items/pickup/safe_haven.xml', name='$item_safe_haven', xml='safe_haven.xml'}, 133 | {path='data/entities/items/pickup/spell_refresh.xml', name='$item_spell_refresh', xml='spell_refresh.xml'}, 134 | {path='data/entities/items/pickup/stonestone.xml', name='$item_stonestone', xml='stonestone.xml'}, 135 | {path='data/entities/items/pickup/summon_portal_broken.xml', name='$action_broken_spell', xml='summon_portal_broken.xml'}, 136 | {path='data/entities/items/pickup/sun/newsun.xml', name='newsun.xml', xml='newsun.xml'}, 137 | {path='data/entities/items/pickup/sun/newsun_dark.xml', name='newsun_dark.xml', xml='newsun_dark.xml'}, 138 | {path='data/entities/items/pickup/sun/sunbaby.xml', name='sunbaby.xml', xml='sunbaby.xml'}, 139 | {path='data/entities/items/pickup/sun/sunegg.xml', name='sunegg.xml', xml='sunegg.xml'}, 140 | {path='data/entities/items/pickup/sun/sunseed.xml', name='$item_sunseed', xml='sunseed.xml'}, 141 | {path='data/entities/items/pickup/sun/sunstone.xml', name='$item_seed_c', xml='sunstone.xml'}, 142 | {path='data/entities/items/pickup/test/pouch.xml', name='$item_potion', xml='pouch.xml'}, 143 | {path='data/entities/items/pickup/test/pouch_static.xml', name='$item_potion', xml='pouch_static.xml'}, 144 | {path='data/entities/items/pickup/thunderstone.xml', name='$item_thunderstone', xml='thunderstone.xml'}, 145 | {path='data/entities/items/pickup/wandstone.xml', name='$item_wandstone', xml='wandstone.xml'}, 146 | {path='data/entities/items/pickup/waterstone.xml', name='$item_waterstone', xml='waterstone.xml'}, 147 | {path='data/entities/items/shop_cape.xml', name='shop_cape.xml', xml='shop_cape.xml'}, 148 | {path='data/entities/items/shop_item.xml', name='shop_item.xml', xml='shop_item.xml'}, 149 | {path='data/entities/items/shop_potion.xml', name='shop_potion.xml', xml='shop_potion.xml'}, 150 | {path='data/entities/items/shop_wand_level_01.xml', name='shop_wand_level_01.xml', xml='shop_wand_level_01.xml'}, 151 | {path='data/entities/items/starting_bomb_wand.xml', name='bomb_wand', xml='starting_bomb_wand.xml'}, 152 | {path='data/entities/items/starting_bomb_wand_rng.xml', name='bomb_wand', xml='starting_bomb_wand_rng.xml'}, 153 | {path='data/entities/items/starting_bomb_wand_rng_daily.xml', name='bomb_wand', xml='starting_bomb_wand_rng_daily.xml'}, 154 | {path='data/entities/items/starting_wand.xml', name='default_gun', xml='starting_wand.xml'}, 155 | {path='data/entities/items/starting_wand_rng.xml', name='default_gun', xml='starting_wand_rng.xml'}, 156 | {path='data/entities/items/starting_wand_rng_daily.xml', name='default_gun', xml='starting_wand_rng_daily.xml'}, 157 | {path='data/entities/items/wand_arpaluu.xml', name='wand_arpaluu.xml', xml='wand_arpaluu.xml'}, 158 | {path='data/entities/items/wand_daily_01.xml', name='wand_daily_01.xml', xml='wand_daily_01.xml'}, 159 | {path='data/entities/items/wand_daily_02.xml', name='wand_daily_02.xml', xml='wand_daily_02.xml'}, 160 | {path='data/entities/items/wand_daily_03.xml', name='wand_daily_03.xml', xml='wand_daily_03.xml'}, 161 | {path='data/entities/items/wand_daily_04.xml', name='wand_daily_04.xml', xml='wand_daily_04.xml'}, 162 | {path='data/entities/items/wand_daily_05.xml', name='wand_daily_05.xml', xml='wand_daily_05.xml'}, 163 | {path='data/entities/items/wand_daily_06.xml', name='wand_daily_06.xml', xml='wand_daily_06.xml'}, 164 | {path='data/entities/items/wand_kiekurakeppi.xml', name='wand_kiekurakeppi.xml', xml='wand_kiekurakeppi.xml'}, 165 | {path='data/entities/items/wand_level_01.xml', name='wand_level_01.xml', xml='wand_level_01.xml'}, 166 | {path='data/entities/items/wand_level_01_better.xml', name='wand_level_01_better.xml', xml='wand_level_01_better.xml'}, 167 | {path='data/entities/items/wand_level_01_p.xml', name='wand_level_01_p.xml', xml='wand_level_01_p.xml'}, 168 | {path='data/entities/items/wand_level_02.xml', name='wand_level_02.xml', xml='wand_level_02.xml'}, 169 | {path='data/entities/items/wand_level_02_better.xml', name='wand_level_02_better.xml', xml='wand_level_02_better.xml'}, 170 | {path='data/entities/items/wand_level_02_p.xml', name='wand_level_02_p.xml', xml='wand_level_02_p.xml'}, 171 | {path='data/entities/items/wand_level_03.xml', name='wand_level_03.xml', xml='wand_level_03.xml'}, 172 | {path='data/entities/items/wand_level_03_better.xml', name='wand_level_03_better.xml', xml='wand_level_03_better.xml'}, 173 | {path='data/entities/items/wand_level_03_p.xml', name='wand_level_03_p.xml', xml='wand_level_03_p.xml'}, 174 | {path='data/entities/items/wand_level_04.xml', name='wand_level_04.xml', xml='wand_level_04.xml'}, 175 | {path='data/entities/items/wand_level_04_better.xml', name='wand_level_04_better.xml', xml='wand_level_04_better.xml'}, 176 | {path='data/entities/items/wand_level_04_p.xml', name='wand_level_04_p.xml', xml='wand_level_04_p.xml'}, 177 | {path='data/entities/items/wand_level_05.xml', name='wand_level_05.xml', xml='wand_level_05.xml'}, 178 | {path='data/entities/items/wand_level_05_better.xml', name='wand_level_05_better.xml', xml='wand_level_05_better.xml'}, 179 | {path='data/entities/items/wand_level_05_p.xml', name='wand_level_05_p.xml', xml='wand_level_05_p.xml'}, 180 | {path='data/entities/items/wand_level_06.xml', name='wand_level_06.xml', xml='wand_level_06.xml'}, 181 | {path='data/entities/items/wand_level_06_better.xml', name='wand_level_06_better.xml', xml='wand_level_06_better.xml'}, 182 | {path='data/entities/items/wand_level_06_p.xml', name='wand_level_06_p.xml', xml='wand_level_06_p.xml'}, 183 | {path='data/entities/items/wand_level_10.xml', name='wand_level_10.xml', xml='wand_level_10.xml'}, 184 | {path='data/entities/items/wand_petri.xml', name='wand_petri.xml', xml='wand_petri.xml'}, 185 | {path='data/entities/items/wand_ruusu.xml', name='wand_ruusu.xml', xml='wand_ruusu.xml'}, 186 | {path='data/entities/items/wand_unshuffle_01.xml', name='wand_unshuffle_01.xml', xml='wand_unshuffle_01.xml'}, 187 | {path='data/entities/items/wand_unshuffle_01_p.xml', name='wand_unshuffle_01_p.xml', xml='wand_unshuffle_01_p.xml'}, 188 | {path='data/entities/items/wand_unshuffle_02.xml', name='wand_unshuffle_02.xml', xml='wand_unshuffle_02.xml'}, 189 | {path='data/entities/items/wand_unshuffle_02_p.xml', name='wand_unshuffle_02_p.xml', xml='wand_unshuffle_02_p.xml'}, 190 | {path='data/entities/items/wand_unshuffle_03.xml', name='wand_unshuffle_03.xml', xml='wand_unshuffle_03.xml'}, 191 | {path='data/entities/items/wand_unshuffle_03_p.xml', name='wand_unshuffle_03_p.xml', xml='wand_unshuffle_03_p.xml'}, 192 | {path='data/entities/items/wand_unshuffle_04.xml', name='wand_unshuffle_04.xml', xml='wand_unshuffle_04.xml'}, 193 | {path='data/entities/items/wand_unshuffle_04_p.xml', name='wand_unshuffle_04_p.xml', xml='wand_unshuffle_04_p.xml'}, 194 | {path='data/entities/items/wand_unshuffle_05.xml', name='wand_unshuffle_05.xml', xml='wand_unshuffle_05.xml'}, 195 | {path='data/entities/items/wand_unshuffle_05_p.xml', name='wand_unshuffle_05_p.xml', xml='wand_unshuffle_05_p.xml'}, 196 | {path='data/entities/items/wand_unshuffle_06.xml', name='wand_unshuffle_06.xml', xml='wand_unshuffle_06.xml'}, 197 | {path='data/entities/items/wand_unshuffle_06_p.xml', name='wand_unshuffle_06_p.xml', xml='wand_unshuffle_06_p.xml'}, 198 | {path='data/entities/items/wand_unshuffle_10.xml', name='wand_unshuffle_10.xml', xml='wand_unshuffle_10.xml'}, 199 | {path='data/entities/items/wand_valtikka.xml', name='wand_valtikka.xml', xml='wand_valtikka.xml'}, 200 | {path='data/entities/items/wand_varpuluuta.xml', name='wand_varpuluuta.xml', xml='wand_varpuluuta.xml'}, 201 | {path='data/entities/items/wand_vasta.xml', name='wand_vasta.xml', xml='wand_vasta.xml'}, 202 | {path='data/entities/items/wand_vihta.xml', name='wand_vihta.xml', xml='wand_vihta.xml'}, 203 | {path='data/entities/items/wands/custom/digger_01.xml', name='fire_wand', xml='digger_01.xml'}, 204 | {path='data/entities/items/wands/experimental/experimental_wand_1.xml', name='experimental_wand_1', xml='experimental_wand_1.xml'}, 205 | {path='data/entities/items/wands/experimental/experimental_wand_1_sprite.xml', name='experimental_wand_1_sprite.xml', xml='experimental_wand_1_sprite.xml'}, 206 | {path='data/entities/items/wands/experimental/experimental_wand_2.xml', name='experimental_wand_2', xml='experimental_wand_2.xml'}, 207 | {path='data/entities/items/wands/experimental/experimental_wand_2_sprite.xml', name='experimental_wand_2_sprite.xml', xml='experimental_wand_2_sprite.xml'}, 208 | {path='data/entities/items/wands/experimental/experimental_wand_3.xml', name='experimental_wand_3', xml='experimental_wand_3.xml'}, 209 | {path='data/entities/items/wands/level_01/base_wand_level_1.xml', name='', xml='base_wand_level_1.xml'}, 210 | {path='data/entities/items/wands/level_01/wand_001.xml', name='', xml='wand_001.xml'}, 211 | {path='data/entities/items/wands/level_01/wand_002.xml', name='', xml='wand_002.xml'}, 212 | {path='data/entities/items/wands/level_01/wand_003.xml', name='', xml='wand_003.xml'}, 213 | {path='data/entities/items/wands/level_01/wand_004.xml', name='', xml='wand_004.xml'}, 214 | {path='data/entities/items/wands/level_01/wand_005.xml', name='', xml='wand_005.xml'}, 215 | {path='data/entities/items/wands/level_01/wand_006.xml', name='', xml='wand_006.xml'}, 216 | {path='data/entities/items/wands/level_01/wand_007.xml', name='', xml='wand_007.xml'}, 217 | {path='data/entities/items/wands/level_01/wand_008.xml', name='', xml='wand_008.xml'}, 218 | {path='data/entities/items/wands/level_01/wand_009.xml', name='', xml='wand_009.xml'}, 219 | {path='data/entities/items/wands/level_01/wand_010.xml', name='wand_010.xml', xml='wand_010.xml'}, 220 | {path='data/entities/items/wands/level_01/wand_011.xml', name='wand_011.xml', xml='wand_011.xml'}, 221 | {path='data/entities/items/wands/level_01/wand_012.xml', name='wand_012.xml', xml='wand_012.xml'}, 222 | {path='data/entities/items/wands/level_01/wand_013.xml', name='wand_013.xml', xml='wand_013.xml'}, 223 | {path='data/entities/items/wands/level_01/wand_014.xml', name='wand_014.xml', xml='wand_014.xml'}, 224 | {path='data/entities/items/wands/level_01/wand_015.xml', name='wand_015.xml', xml='wand_015.xml'}, 225 | {path='data/entities/items/wands/level_01/wand_016.xml', name='wand_016.xml', xml='wand_016.xml'}, 226 | {path='data/entities/items/wands/level_01/wand_017.xml', name='wand_017.xml', xml='wand_017.xml'}, 227 | {path='data/entities/items/wands/level_01_p/util_001.xml', name='', xml='util_001.xml'}, 228 | {path='data/entities/items/wands/level_01_p/util_002.xml', name='', xml='util_002.xml'}, 229 | {path='data/entities/items/wands/level_01_p/util_003.xml', name='', xml='util_003.xml'}, 230 | {path='data/entities/items/wands/level_01_p/util_004.xml', name='', xml='util_004.xml'}, 231 | {path='data/entities/items/wands/level_01_p/util_005.xml', name='', xml='util_005.xml'}, 232 | {path='data/entities/items/wands/level_01_p/util_006.xml', name='', xml='util_006.xml'}, 233 | {path='data/entities/items/wands/level_01_p/wand_001.xml', name='', xml='wand_001.xml'}, 234 | {path='data/entities/items/wands/level_01_p/wand_002.xml', name='', xml='wand_002.xml'}, 235 | {path='data/entities/items/wands/level_01_p/wand_003.xml', name='', xml='wand_003.xml'}, 236 | {path='data/entities/items/wands/level_01_p/wand_004.xml', name='', xml='wand_004.xml'}, 237 | {path='data/entities/items/wands/level_01_p/wand_005.xml', name='', xml='wand_005.xml'}, 238 | {path='data/entities/items/wands/level_01_p/wand_006.xml', name='', xml='wand_006.xml'}, 239 | {path='data/entities/items/wands/level_01_p/wand_007.xml', name='', xml='wand_007.xml'}, 240 | {path='data/entities/items/wands/level_01_p/wand_008.xml', name='', xml='wand_008.xml'}, 241 | {path='data/entities/items/wands/level_01_p/wand_009.xml', name='', xml='wand_009.xml'}, 242 | {path='data/entities/items/wands/wand_good/wand_good_1.xml', name='wand_good_1', xml='wand_good_1.xml'}, 243 | {path='data/entities/items/wands/wand_good/wand_good_1_sprite.xml', name='wand_good_1_sprite.xml', xml='wand_good_1_sprite.xml'}, 244 | {path='data/entities/items/wands/wand_good/wand_good_2.xml', name='wand_good_2', xml='wand_good_2.xml'}, 245 | {path='data/entities/items/wands/wand_good/wand_good_2_sprite.xml', name='wand_good_2_sprite.xml', xml='wand_good_2_sprite.xml'}, 246 | {path='data/entities/items/wands/wand_good/wand_good_3.xml', name='wand_good_3', xml='wand_good_3.xml'}, 247 | {path='data/entities/items/wands/wand_good/wand_good_3_sprite.xml', name='wand_good_3_sprite.xml', xml='wand_good_3_sprite.xml'} 248 | } -------------------------------------------------------------------------------- /data/hax/special_spawnables.lua: -------------------------------------------------------------------------------- 1 | -- special spawnable items that don't get found by `gen_spawnlist.py` 2 | special_spawnables = { 3 | {path='data/entities/animals/boss_alchemist/key.xml', name='Crystal Key', xml="key.xml"}, 4 | {path='data/entities/animals/boss_centipede/sampo.xml', name='Sampo', xml="sampo.xml"}, 5 | } -------------------------------------------------------------------------------- /data/hax/superhackykb.lua: -------------------------------------------------------------------------------- 1 | print("Loading hacky KB?") 2 | 3 | if _hacky_keyboard_defined then 4 | return 5 | end 6 | 7 | _hacky_keyboard_defined = true 8 | 9 | hack_type = function() 10 | return "" 11 | end 12 | 13 | if not require then 14 | print("No require? Urgh.") 15 | return 16 | end 17 | 18 | local ffi = require('ffi') 19 | if not ffi then 20 | print("No FFI? Well that's a pain.") 21 | return 22 | end 23 | 24 | _keyboard_present = true 25 | 26 | ffi.cdef([[ 27 | const uint8_t* SDL_GetKeyboardState(int* numkeys); 28 | uint32_t SDL_GetKeyFromScancode(uint32_t scancode); 29 | char* SDL_GetScancodeName(uint32_t scancode); 30 | char* SDL_GetKeyName(uint32_t key); 31 | ]]) 32 | _SDL = ffi.load('SDL2.dll') 33 | 34 | local code_to_a = {} 35 | local shifts = {} 36 | 37 | for i = 0, 284 do 38 | local keycode = _SDL.SDL_GetKeyFromScancode(i) 39 | if keycode > 0 then 40 | local keyname = ffi.string(_SDL.SDL_GetKeyName(keycode)) 41 | if keyname and #keyname > 0 then 42 | code_to_a[i] = keyname:lower() 43 | if keyname:lower():find("shift") then 44 | table.insert(shifts, i) 45 | end 46 | end 47 | end 48 | end 49 | 50 | local prev_state = {} 51 | for i = 0, 284 do 52 | prev_state[i] = 0 53 | end 54 | 55 | function hack_update_keys() 56 | local keys = _SDL.SDL_GetKeyboardState(nil) 57 | local pressed = {} 58 | -- start at scancode 1 because we don't care about "UNKNOWN" 59 | for scancode = 1, 284 do 60 | if keys[scancode] > 0 and prev_state[scancode] <= 0 then 61 | pressed[#pressed+1] = code_to_a[scancode] 62 | end 63 | prev_state[scancode] = keys[scancode] 64 | end 65 | local shift_held = false 66 | for _, shiftcode in ipairs(shifts) do 67 | if keys[shiftcode] > 0 then 68 | shift_held = true 69 | break 70 | end 71 | end 72 | return pressed, shift_held 73 | end 74 | 75 | local REPLACEMENTS = { 76 | space = " " 77 | } 78 | 79 | hack_type = function(current_str, no_shift) 80 | local pressed, shift_held = hack_update_keys() 81 | local hit_enter = false 82 | for _, key in ipairs(pressed) do 83 | if (no_shift or shift_held) and REPLACEMENTS[key] then 84 | current_str = current_str .. REPLACEMENTS[key] 85 | elseif (no_shift or shift_held) and (#key == 1) then 86 | current_str = current_str .. key 87 | elseif key == "backspace" then 88 | current_str = current_str:sub(1,-2) 89 | elseif key == "enter" or key == "return" then 90 | hit_enter = true 91 | end 92 | end 93 | return current_str, hit_enter 94 | end 95 | 96 | print("Hacky KB loaded?") -------------------------------------------------------------------------------- /data/hax/utils.lua: -------------------------------------------------------------------------------- 1 | function get_player() 2 | return EntityGetWithTag("player_unit")[1] 3 | end 4 | 5 | function get_player_pos() 6 | local player = get_player() 7 | if not player then return 0, 0 end 8 | return EntityGetTransform(player) 9 | end 10 | 11 | function enable_ai(e, enabled) 12 | local ai = EntityGetFirstComponent(e, "AnimalAIComponent") 13 | if not ai then print("no ai??") end 14 | --ComponentSetValue( ai, "_enabled", (enabled and "1") or "0" ) 15 | --ComponentSetValue( ai, "enabled", (enabled and "1") or "0" ) 16 | EntitySetComponentIsEnabled(e, ai, enabled) 17 | end 18 | 19 | function teleport(x, y) 20 | EntitySetTransform(get_player(), x, y) 21 | end 22 | 23 | function get_health() 24 | local dm = EntityGetComponent(get_player(), "DamageModelComponent")[1] 25 | return ComponentGetValue(dm, "hp"), ComponentGetValue(dm, "max_hp") 26 | end 27 | 28 | function set_health(cur_hp, max_hp) 29 | local damagemodels = EntityGetComponent(get_player(), "DamageModelComponent") 30 | for _, damagemodel in ipairs(damagemodels or {}) do 31 | ComponentSetValue(damagemodel, "max_hp", max_hp) 32 | ComponentSetValue(damagemodel, "hp", cur_hp) 33 | end 34 | end 35 | 36 | function quick_heal() 37 | local _, max_hp = get_health() 38 | set_health(max_hp, max_hp) 39 | end 40 | 41 | function set_money(amt) 42 | local wallet = EntityGetFirstComponent(get_player(), "WalletComponent") 43 | ComponentSetValue2(wallet, "money", amt) 44 | end 45 | 46 | function get_money() 47 | local wallet = EntityGetFirstComponent(get_player(), "WalletComponent") 48 | return ComponentGetValue2(wallet, "money") 49 | end 50 | 51 | function twiddle_money(delta) 52 | local wallet = EntityGetFirstComponent(get_player(), "WalletComponent") 53 | local current = ComponentGetValue2(wallet, "money") 54 | ComponentSetValue2(wallet, "money", math.max(0, current+delta)) 55 | end 56 | 57 | function spawn_entity(ename, offset_x, offset_y) 58 | local x, y = get_player_pos() 59 | x = x + (offset_x or 0) 60 | y = y + (offset_y or 0) 61 | return EntityLoad(ename, x, y) 62 | end 63 | spawn_item = spawn_entity 64 | 65 | function empty_container_of_materials(idx) 66 | for _ = 1, 1000 do -- avoid infinite loop 67 | local material = GetMaterialInventoryMainMaterial(idx) 68 | if material <= 0 then break end 69 | local matname = CellFactory_GetName(material) 70 | AddMaterialInventoryMaterial(idx, matname, 0) 71 | end 72 | end 73 | 74 | function spawn_potion(material, quantity, kind) 75 | local x, y = get_player_pos() 76 | quantity = quantity or 1000 77 | local entity 78 | if kind == nil or kind == "potion" then 79 | entity = EntityLoad("data/entities/items/pickup/potion_empty.xml", x, y) 80 | else -- kind == "pouch" 81 | entity = EntityLoad("data/entities/items/pickup/powder_stash.xml", x, y) 82 | empty_container_of_materials(entity) 83 | quantity = quantity * 1.5 84 | end 85 | AddMaterialInventoryMaterial(entity, material, quantity) 86 | end 87 | 88 | function spawn_perk(perk_id, auto_pickup_entity) 89 | local x, y = get_player_pos() 90 | local perk_entity = perk_spawn(x, y - 8, perk_id) 91 | if auto_pickup_entity then 92 | perk_pickup(perk_entity, auto_pickup_entity, nil, true, false) 93 | end 94 | end 95 | 96 | function set_tourist_mode(enabled) 97 | local herd = (enabled and "healer") or "player" 98 | GenomeSetHerdId(get_player(), herd) 99 | end 100 | 101 | function hello() 102 | GamePrintImportant("Hello", "Hello") 103 | GamePrint("Hello") 104 | print("Hello") 105 | end 106 | 107 | function get_closest_entity(px, py, tag) 108 | if not py then 109 | tag = px 110 | px, py = get_player_pos() 111 | end 112 | return EntityGetClosestWithTag( px, py, tag) 113 | end 114 | 115 | function get_entity_mouse(tag) 116 | local mx, my = DEBUG_GetMouseWorld() 117 | return get_closest_entity(mx, my, tag or "hittable") 118 | end 119 | 120 | function print_component_info(c) 121 | local frags = {"<" .. ComponentGetTypeName(c) .. ">"} 122 | local members = ComponentGetMembers(c) 123 | if not members then return end 124 | for k, v in pairs(members) do 125 | table.insert(frags, k .. ': ' .. tostring(v)) 126 | end 127 | print(table.concat(frags, '\n')) 128 | end 129 | 130 | function get_vector_value(comp, member, kind) 131 | kind = kind or "float" 132 | local n = ComponentGetVectorSize( comp, member, kind ) 133 | if not n then return nil end 134 | local ret = {}; 135 | for i = 1, n do 136 | ret[i] = ComponentGetVectorValue(comp, member, kind, i-1) or "nil" 137 | end 138 | return ret 139 | end 140 | 141 | function print_vector_value(...) 142 | local v = get_vector_value(...) 143 | if not v then return nil end 144 | return "{" .. table.concat(v, ", ") .. "}" 145 | end 146 | 147 | function print_detailed_component_info(c) 148 | local members = ComponentGetMembers(c) 149 | if not members then return end 150 | local frags = {} 151 | for k, v in pairs(members) do 152 | if (not v) or #v == 0 then 153 | local mems = ComponentObjectGetMembers(c, k) 154 | if mems then 155 | table.insert(frags, k .. ">") 156 | for k2, v2 in pairs(mems) do 157 | table.insert(frags, " " .. k2 .. ": " .. tostring(v2)) 158 | end 159 | else 160 | v = print_vector_value(c, k) 161 | end 162 | end 163 | table.insert(frags, k .. ': ' .. tostring(v)) 164 | end 165 | print(table.concat(frags, '\n')) 166 | 167 | end 168 | 169 | function print_entity_info(e) 170 | local comps = EntityGetAllComponents(e) 171 | if not comps then 172 | print("Invalid entity?") 173 | return 174 | end 175 | for idx, comp in ipairs(comps) do 176 | print(comp, "-----------------") 177 | print_component_info(comp) 178 | end 179 | end 180 | 181 | function list_components(e) 182 | local comps = EntityGetAllComponents(e) 183 | if not comps then 184 | print("Invalid entity?") 185 | return 186 | end 187 | for idx, comp in ipairs(comps) do 188 | print(comp .. " : " .. ComponentGetTypeName(comp)) 189 | end 190 | end 191 | 192 | function list_funcs(filter) 193 | local ff = {} 194 | for k, v in pairs(getfenv()) do 195 | local first_letter = k:sub(1,1) 196 | if first_letter:upper() == first_letter then 197 | if (not filter) or k:lower():find(filter:lower()) then 198 | table.insert(ff, k) 199 | end 200 | end 201 | end 202 | table.sort(ff) 203 | print(table.concat(ff, "\n")) 204 | end 205 | 206 | function get_child_info(e) 207 | local children = EntityGetAllChildren(e) 208 | for _, child in ipairs(children) do 209 | print(child, EntityGetName(child) or "[no name]") 210 | end 211 | end 212 | 213 | function do_here(fn) 214 | local f = loadfile(fn) 215 | if type(f) ~= "function" then 216 | print("Loading error; check logger.txt for details.") 217 | end 218 | setfenv(f, getfenv()) 219 | f() 220 | end 221 | 222 | function round(v) 223 | local upper = math.ceil(v) 224 | local lower = math.floor(v) 225 | if math.abs(v - upper) < math.abs(v - lower) then 226 | return upper 227 | else 228 | return lower 229 | end 230 | end 231 | 232 | function resolve_localized_name(s, default) 233 | if s:sub(1,1) ~= "$" then return s end 234 | local rep = GameTextGet(s) 235 | if rep and rep ~= "" then return rep else return default or s end 236 | end 237 | 238 | function localize_material(mat) 239 | local n = GameTextGet("$mat_" .. mat) 240 | if n and n ~= "" then return n else return "[" .. mat .. "]" end 241 | end -------------------------------------------------------------------------------- /data/hax/wand_empty.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 38 | 43 | 44 | 46 | 47 | 48 | 49 | 53 | 54 | 55 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /data/hax/wand_hax.lua: -------------------------------------------------------------------------------- 1 | dofile("data/scripts/gun/procedural/gun_procedural.lua") 2 | 3 | local function gen_gun() 4 | local entity_id = GetUpdatedEntityID() 5 | local x, y = EntityGetTransform( entity_id ) 6 | SetRandomSeed( x, y ) 7 | 8 | local cost, level = 0, 6 9 | 10 | local ability_comp = EntityGetFirstComponent( entity_id, "AbilityComponent" ) 11 | 12 | cost = 0 13 | 14 | local gun = { } 15 | gun["cost"] = cost 16 | gun["deck_capacity"] = 50 17 | gun["actions_per_round"] = 50 18 | gun["reload_time"] = 60 19 | gun["shuffle_deck_when_empty"] = 0 20 | gun["fire_rate_wait"] = 60 21 | gun["spread_degrees"] = 0 22 | gun["speed_multiplier"] = 1 23 | gun["prob_unshuffle"] = 0.1 24 | gun["prob_draw_many"] = 0.15 25 | gun["mana_charge_speed"] = 10000 26 | gun["mana_max"] = 10000 27 | gun["force_unshuffle"] = 1 28 | 29 | 30 | local name = "HAXXXX" --ComponentGetValue( ability_comp, "ui_name" ) 31 | 32 | -- SetItemSprite( entity_id, ability_comp, "data/items_gfx/gungen_guns/submachinegun_", Random( 0, 7 ) ) 33 | ComponentSetValue( ability_comp, "ui_name", name ) 34 | ComponentObjectSetValue( ability_comp, "gun_config", "actions_per_round", gun["actions_per_round"] ) 35 | ComponentObjectSetValue( ability_comp, "gun_config", "reload_time", gun["reload_time"] ) 36 | ComponentObjectSetValue( ability_comp, "gun_config", "deck_capacity", gun["deck_capacity"] ) 37 | ComponentObjectSetValue( ability_comp, "gun_config", "shuffle_deck_when_empty", gun["shuffle_deck_when_empty"] ) 38 | ComponentObjectSetValue( ability_comp, "gunaction_config", "fire_rate_wait", gun["fire_rate_wait"] ) 39 | ComponentObjectSetValue( ability_comp, "gunaction_config", "spread_degrees", gun["spread_degrees"] ) 40 | ComponentObjectSetValue( ability_comp, "gunaction_config", "speed_multiplier", gun["speed_multiplier"] ) 41 | ComponentSetValue( ability_comp, "mana_charge_speed", gun["mana_charge_speed"]) 42 | ComponentSetValue( ability_comp, "mana_max", gun["mana_max"]) 43 | ComponentSetValue( ability_comp, "mana", gun["mana_max"]) 44 | 45 | ComponentSetValue( ability_comp, "item_recoil_recovery_speed", 15.0 ) -- TODO: implement logic for setting this 46 | 47 | -- stuff in the gun 48 | local good_cards = 5 49 | if( Random(0,100) < 7 ) then good_cards = Random(20,50) end 50 | 51 | if( is_rare == 1 ) then 52 | good_cards = good_cards * 2 53 | end 54 | 55 | local orig_level = level 56 | level = level - 1 57 | local deck_capacity = gun["deck_capacity"] 58 | local actions_per_round = gun["actions_per_round"] 59 | local card_count = Random( 1, 3 ) 60 | local bullet_card = GetRandomActionWithType( x, y, level, ACTION_TYPE_PROJECTILE, 0 ) 61 | local random_bullets = 0 62 | local good_card_count = 0 63 | 64 | if( Random(0,100) < 50 and card_count < 3 ) then card_count = card_count + 1 end 65 | 66 | if( Random(0,100) < 10 or is_rare == 1 ) then 67 | card_count = card_count + Random( 1, 2 ) 68 | end 69 | 70 | good_cards = Random( 5, 45 ) 71 | card_count = Random( 0.51 * deck_capacity, deck_capacity ) 72 | card_count = clamp( card_count, 1, deck_capacity-1 ) 73 | 74 | -- card count is in between 1 and 6 75 | 76 | if( Random(0,100) < (orig_level*10)-5 ) then 77 | random_bullets = 1 78 | end 79 | 80 | if( Random( 0, 100 ) < 4 or is_rare == 1 ) then 81 | local card = 0 82 | local p = Random(0,100) 83 | if( p < 77 ) then 84 | card = GetRandomActionWithType( x, y, level+1, ACTION_TYPE_MODIFIER, 666 ) 85 | elseif( p < 94 ) then 86 | card = GetRandomActionWithType( x, y, level+1, ACTION_TYPE_DRAW_MANY, 666 ) 87 | good_card_count = good_card_count + 1 88 | else 89 | card = GetRandomActionWithType( x, y, level+1, ACTION_TYPE_PROJECTILE, 666 ) 90 | end 91 | AddGunActionPermanent( entity_id, card ) 92 | end 93 | 94 | 95 | for i=1,card_count do 96 | if( Random(0,100) < good_cards ) then 97 | -- if actions_per_round == 1 and the first good card, then make sure it's a draw x 98 | local card = 0 99 | if( good_card_count == 0 and actions_per_round == 1 ) then 100 | card = GetRandomActionWithType( x, y, level, ACTION_TYPE_DRAW_MANY, i ) 101 | good_card_count = good_card_count + 1 102 | else 103 | if( Random(0,100) < 83 ) then 104 | card = GetRandomActionWithType( x, y, level, ACTION_TYPE_MODIFIER, i ) 105 | else 106 | card = GetRandomActionWithType( x, y, level, ACTION_TYPE_DRAW_MANY, i ) 107 | end 108 | end 109 | 110 | AddGunAction( entity_id, card ) 111 | else 112 | AddGunAction( entity_id, bullet_card ) 113 | if( random_bullets == 1 ) then 114 | bullet_card = GetRandomActionWithType( x, y, level, ACTION_TYPE_PROJECTILE, i ) 115 | end 116 | end 117 | end 118 | 119 | local wand = GetWand( gun ) 120 | 121 | SetWandSprite( entity_id, ability_comp, wand.file, wand.grip_x, wand.grip_y, (wand.tip_x - wand.grip_x), (wand.tip_y - wand.grip_y) ) 122 | -- SetItemSprite( entity_id, ability_comp, "data/items_gfx/wands/wand_", Random( 0, 999 ) ) 123 | 124 | -- this way: 125 | -- AddGunActionPermanent( entity_id, "ELECTRIC_CHARGE" ) 126 | end 127 | gen_gun() -------------------------------------------------------------------------------- /data/hax/wand_hax.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 38 | 43 | 44 | 46 | 47 | 48 | 49 | 53 | 54 | 55 | 61 | 62 | 63 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /gen_spawnlist.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | import re 4 | 5 | ITEM_NAME_PATT = re.compile(r'item_name\s*=\s*"([^"]*)"') 6 | 7 | def find_item_name(raw_xml): 8 | """Finds the `item_name` out of an XML 9 | 10 | We don't use a real XML library because Noita's XML files are 11 | non-standard. Instead we just regex search for this key-value pair 12 | like naive CS students. 13 | """ 14 | match = ITEM_NAME_PATT.search(raw_xml) 15 | if match is None: 16 | return None 17 | return match.groups()[0] 18 | 19 | def add_item(item_list, filename, subpath): 20 | # assume everything spawnable is an XML 21 | if filename[-4:].lower() != ".xml": 22 | return 23 | with open(filename, "rt") as src: 24 | data = src.read() 25 | ui_name = find_item_name(data) 26 | raw_name = os.path.split(filename)[-1] 27 | if ui_name is None: 28 | ui_name = raw_name 29 | item_list.append((subpath, ui_name, raw_name)) 30 | 31 | def find_items(rootdir, prefix=""): 32 | all_items = [] 33 | for root, _, files in os.walk(rootdir): 34 | for fname in files: 35 | fullpath = os.path.join(root, fname) 36 | subpath = prefix + fullpath.replace(rootdir, "").replace("\\", "/") 37 | add_item(all_items, fullpath, subpath) 38 | return all_items 39 | 40 | def escape_quotes(s): 41 | """Escape single quotes 42 | 43 | I'm not sure any Noita item names actually use single quotes, but better 44 | safe than sorry 45 | """ 46 | return s.replace("'", "\\'") 47 | 48 | def item_to_lua(item): 49 | return f"{{path='{escape_quotes(item[0])}', name='{escape_quotes(item[1])}', xml='{item[2]}'}}" 50 | 51 | def item_list_to_lua(item_list): 52 | body = ",\n ".join(item_to_lua(item) for item in item_list) 53 | return "spawn_list = {\n " + body + "\n}" 54 | 55 | if __name__ == "__main__": 56 | if len(sys.argv) >= 2: 57 | path = sys.argv[1] 58 | else: 59 | path = os.path.abspath(os.path.expandvars(r'%LOCALAPPDATA%/../LocalLow/Nolla_Games_Noita/data')) 60 | print("Using path to Noita data: ", path) 61 | items_path = os.path.join(path, "entities/items") 62 | print("Path to items: ", items_path) 63 | items = find_items(items_path, "data/entities/items") 64 | items = sorted(items) # sort by path I guess? 65 | print(f"Found {len(items)} items.") 66 | lua = """ 67 | -- AUTOGENERATED! DO NOT EDIT DIRECTLY! 68 | -- RUN 'gen_spawnlist.py' TO REGENERATE! (requires unpacked data!) 69 | -- MANUALLY ADD special items to 'special_spawnables.lua'! 70 | """ + item_list_to_lua(items) 71 | with open("data/hax/spawnables.lua", "wt") as dest: 72 | dest.write(lua) -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | -- stash the environment! (mainly to keep ModTextFileGetContent around!) 2 | cheatgui_stash = {} 3 | for k, v in pairs(_G) do 4 | cheatgui_stash[k] = v 5 | end 6 | 7 | function OnWorldPostUpdate() 8 | if _cheat_gui_main then _cheat_gui_main() end 9 | end 10 | 11 | function OnPlayerSpawned( player_entity ) 12 | print("OnPlayerSpawned require check:") 13 | if not require then 14 | print("NO require.") 15 | else 16 | print("YES require.") 17 | end 18 | dofile("data/hax/cheatgui.lua") 19 | end 20 | -------------------------------------------------------------------------------- /mod.xml: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /screenshot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/probable-basilisk/cheatgui/225c0c7accb6e6eb46bab188ef49bf7ec678c844/screenshot.jpg -------------------------------------------------------------------------------- /www/css/codemirror-index.css: -------------------------------------------------------------------------------- 1 | html, body { 2 | height: 100%; 3 | background-color: #000000; 4 | } 5 | 6 | body { 7 | margin: 0; 8 | padding: 0; 9 | } 10 | 11 | #content-area { 12 | overflow: hidden; 13 | position: relative; 14 | height: 100vh; 15 | display: flex; 16 | flex-direction: column; 17 | width: 100%; 18 | will-change: overflow; 19 | } 20 | 21 | .console-column { 22 | width: 100%; 23 | border-right: 1px solid #888; 24 | overflow: auto; 25 | height: auto; 26 | } 27 | 28 | .log-column { 29 | background-color: #282a36; 30 | color: #aaa; 31 | border-left: 1px solid #888; 32 | overflow: auto; 33 | height: auto; 34 | flex: 1; 35 | padding-left: 10px; 36 | font-family: monospace; 37 | font-size: 8pt; 38 | } 39 | 40 | .log-item:nth-child(odd) { 41 | background-color: #1B1D29; 42 | } 43 | 44 | .message { 45 | font-weight: normal !important; 46 | } 47 | 48 | .error { 49 | color: #FF0000 !important; 50 | font-weight: bold; 51 | background-color: #000; 52 | } 53 | 54 | .row { 55 | flex: 1; 56 | margin: 10px; 57 | background-color: #111; 58 | } 59 | 60 | .linerow { 61 | flex: 0.05; 62 | margin: 10px; 63 | } -------------------------------------------------------------------------------- /www/css/themes/dracula.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Name: dracula 4 | Author: Michael Kaminsky (http://github.com/mkaminsky11) 5 | 6 | Original dracula color scheme by Zeno Rocha (https://github.com/zenorocha/dracula-theme) 7 | 8 | */ 9 | 10 | 11 | .cm-s-dracula, .cm-s-dracula .CodeMirror-gutters { 12 | background-color: #282a36 !important; 13 | color: #f8f8f2 !important; 14 | border: none; 15 | } 16 | .cm-s-dracula .CodeMirror-gutters { color: #282a36; } 17 | .cm-s-dracula .CodeMirror-cursor { border-left: solid thin #f8f8f0; } 18 | .cm-s-dracula .CodeMirror-linenumber { color: #6D8A88; } 19 | .cm-s-dracula.CodeMirror-focused div.CodeMirror-selected { background: rgba(255, 255, 255, 0.10); } 20 | .cm-s-dracula .CodeMirror-line::selection, .cm-s-dracula .CodeMirror-line > span::selection, .cm-s-dracula .CodeMirror-line > span > span::selection { background: rgba(255, 255, 255, 0.10); } 21 | .cm-s-dracula .CodeMirror-line::-moz-selection, .cm-s-dracula .CodeMirror-line > span::-moz-selection, .cm-s-dracula .CodeMirror-line > span > span::-moz-selection { background: rgba(255, 255, 255, 0.10); } 22 | .cm-s-dracula span.cm-comment { color: #6272a4; } 23 | .cm-s-dracula span.cm-string, .cm-s-dracula span.cm-string-2 { color: #f1fa8c; } 24 | .cm-s-dracula span.cm-number { color: #bd93f9; } 25 | .cm-s-dracula span.cm-variable { color: white; } 26 | .cm-s-dracula span.cm-variable-2 { color: #50fa7b; } 27 | .cm-s-dracula span.cm-def { color: #ffb86c; } 28 | .cm-s-dracula span.cm-keyword { color: #ff79c6; } 29 | .cm-s-dracula span.cm-operator { color: #ff79c6; } 30 | .cm-s-dracula span.cm-keyword { color: #ff79c6; } 31 | .cm-s-dracula span.cm-atom { color: #bd93f9; } 32 | .cm-s-dracula span.cm-meta { color: #f8f8f2; } 33 | .cm-s-dracula span.cm-tag { color: #ff79c6; } 34 | .cm-s-dracula span.cm-attribute { color: #50fa7b; } 35 | .cm-s-dracula span.cm-qualifier { color: #50fa7b; } 36 | .cm-s-dracula span.cm-property { color: #66d9ef; } 37 | .cm-s-dracula span.cm-builtin { color: #50fa7b; } 38 | .cm-s-dracula span.cm-variable-3 { color: #50fa7b; } 39 | 40 | .cm-s-dracula .CodeMirror-activeline-background { background: rgba(255,255,255,0.1); } 41 | .cm-s-dracula .CodeMirror-matchingbracket { text-decoration: underline; color: white !important; } -------------------------------------------------------------------------------- /www/css/themes/eclipse.css: -------------------------------------------------------------------------------- 1 | .cm-s-eclipse span.cm-meta {color: #FF1717;} 2 | .cm-s-eclipse span.cm-keyword { line-height: 1em; font-weight: bold; color: #7F0055; } 3 | .cm-s-eclipse span.cm-atom {color: #219;} 4 | .cm-s-eclipse span.cm-number {color: #164;} 5 | .cm-s-eclipse span.cm-def {color: #00f;} 6 | .cm-s-eclipse span.cm-variable {color: black;} 7 | .cm-s-eclipse span.cm-variable-2 {color: #0000C0;} 8 | .cm-s-eclipse span.cm-variable-3 {color: #0000C0;} 9 | .cm-s-eclipse span.cm-property {color: black;} 10 | .cm-s-eclipse span.cm-operator {color: black;} 11 | .cm-s-eclipse span.cm-comment {color: #3F7F5F;} 12 | .cm-s-eclipse span.cm-string {color: #2A00FF;} 13 | .cm-s-eclipse span.cm-string-2 {color: #f50;} 14 | .cm-s-eclipse span.cm-error {color: #f00;} 15 | .cm-s-eclipse span.cm-qualifier {color: #555;} 16 | .cm-s-eclipse span.cm-builtin {color: #30a;} 17 | .cm-s-eclipse span.cm-bracket {color: #cc7;} 18 | .cm-s-eclipse span.cm-tag {color: #170;} 19 | .cm-s-eclipse span.cm-attribute {color: #00c;} 20 | .cm-s-eclipse span.cm-link {color: #219;} 21 | 22 | .cm-s-eclipse .CodeMirror-matchingbracket { 23 | border:1px solid grey; 24 | color:black !important;; 25 | } 26 | -------------------------------------------------------------------------------- /www/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | noitacon 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 |
22 |
23 |
24 |
25 |
32 |
33 | 34 | 35 | -------------------------------------------------------------------------------- /www/js/noitaconsole.js: -------------------------------------------------------------------------------- 1 | $(function(){ 2 | initCodeMirror(); 3 | initConnection(); 4 | }); 5 | 6 | var codeWindow = null; 7 | var connection = null; 8 | var connected = false; 9 | var repl = null; 10 | var lineWindow = null; 11 | var fit = null; 12 | 13 | var commandHistory = []; 14 | 15 | function ansiRGB(r, g, b) { 16 | return `\x1b[38;2;${r};${g};${b}m` 17 | } 18 | 19 | function ansiBgRGB(r, g, b) { 20 | return `\x1b[48;2;${r};${g};${b}m` 21 | } 22 | 23 | function ansiReset() { 24 | return '\x1b[0m' 25 | } 26 | 27 | 28 | function getToken() { 29 | var urlParams = new URLSearchParams(window.location.search); 30 | return urlParams.get('token'); 31 | } 32 | 33 | function initConnection(url) { 34 | if(!url) { 35 | url = "ws://localhost:9777" 36 | } 37 | console.log("Connecting to url " + url); 38 | 39 | connection = new WebSocket(url); 40 | connection.addEventListener('open', function (event) { 41 | console.log('Connected: ' + url); 42 | connected = true; 43 | connection.send('AUTH "' + getToken() + '"'); 44 | replPrint("SYS> WS connected."); 45 | }); 46 | 47 | connection.addEventListener('close', function (event) { 48 | replPrint("SYS> WS closed."); 49 | }); 50 | 51 | connection.addEventListener('message', function (event) { 52 | replPrint(event.data); 53 | }); 54 | } 55 | 56 | function remoteEval(code) { 57 | if(code[0] === '!') { 58 | let parts = code.slice(1).split(" "); 59 | if(parts[0] === "connect") { 60 | initConnection(parts[1]); 61 | } else { 62 | replPrint("Unknown local command: " + parts[0]); 63 | } 64 | } else if(connected) { 65 | connection.send(code); 66 | } 67 | } 68 | 69 | function longestSharedPrefix(a, b) { 70 | let nchars = Math.min(a.length, b.length); 71 | let prefixLength = 0; 72 | for(prefixLength = 0; prefixLength < nchars; ++prefixLength) { 73 | if(a[prefixLength] != b[prefixLength]) { 74 | break; 75 | } 76 | } 77 | return a.slice(0, prefixLength); 78 | } 79 | 80 | function longestSetPrefix(opts) { 81 | if(opts.length == 0) { 82 | return "" 83 | } 84 | let curPrefix = opts[0]; 85 | for(let idx = 1; idx < opts.length; ++idx) { 86 | curPrefix = longestSharedPrefix(curPrefix, opts[idx]); 87 | } 88 | return curPrefix; 89 | } 90 | 91 | function replPrint(message) { 92 | message = message.replace(/\n/g, "\r\n"); 93 | if(message.indexOf("ERR>") == 0) { 94 | message = ansiRGB(200, 0, 0) + message + ansiReset(); 95 | } else if(message.indexOf("RES>") == 0) { 96 | message = ansiRGB(100, 100, 255) + message + ansiReset(); 97 | } else if(message.indexOf("EVAL>") == 0) { 98 | message = ansiRGB(100, 255, 100) + message + ansiReset(); 99 | } else if(message.indexOf("HELP>") == 0) { 100 | message = ansiRGB(200, 255, 200) + message + ansiReset(); 101 | } else if(message.indexOf("GAME>") == 0) { 102 | message = ansiRGB(200, 200, 200) + message + ansiReset(); 103 | } else if(message.indexOf("COM>") == 0) { 104 | // see how many suggestions we got 105 | console.log("Got completion?"); 106 | let parts = message.slice(4).split(" "); 107 | let prefix = parts[0]; 108 | let opts = parts[1].split(","); 109 | if(opts.length == 1 && opts[0] != "") { 110 | // fill in this completion 111 | lineWindow.setValue(prefix + opts[0]); 112 | lineWindow.setCursor(lineWindow.lineCount(), 0); 113 | } else if(opts.length > 1) { 114 | let completion = longestSetPrefix(opts); 115 | lineWindow.setValue(prefix + completion); 116 | lineWindow.setCursor(lineWindow.lineCount(), 0); 117 | } 118 | message = ansiRGB(150, 150, 150) + message + ansiReset(); 119 | } 120 | repl.writeln(message); 121 | } 122 | 123 | function initCodeMirror() { 124 | codeWindow = CodeMirror.fromTextArea(document.getElementById("code"), { 125 | value: "-- Put multiline Lua stuff here\n", 126 | mode: "lua", 127 | theme: "dracula", 128 | lineNumbers: true, 129 | tabSize: 2 130 | }); 131 | 132 | codeWindow.setOption("extraKeys", { 133 | "Shift-Enter": function(cm) { 134 | remoteEval(cm.getValue()); 135 | replPrint("EVAL> [buffer]"); 136 | }, 137 | "Tab": function(cm) { 138 | cm.execCommand("insertSoftTab"); 139 | } 140 | }); 141 | 142 | lineWindow = CodeMirror.fromTextArea(document.getElementById("replinput"), { 143 | value: "", 144 | mode: "lua", 145 | theme: "dracula" 146 | }); 147 | 148 | lineWindow.setOption("extraKeys", { 149 | "Enter": function(cm) { 150 | let val = cm.getValue(); 151 | if(val == "") { 152 | return; 153 | } 154 | if(val.slice(-1) == "?") { 155 | val = 'help("' + val.slice(0,-1) + '")' 156 | } 157 | remoteEval(val); 158 | if(commandHistory.indexOf(val) < 0) { 159 | commandHistory.push(val); 160 | } 161 | replPrint("EVAL> " + val); 162 | cm.setValue(""); 163 | }, 164 | "Tab": function(cm) { 165 | console.log("TAB"); 166 | const val = cm.getValue().trim(); 167 | if(val == "") { 168 | return; 169 | } 170 | // Note that [=[ some string ]=] is a special Lua 171 | // string literal that allows nesting of other string 172 | // literals, including the more typical [[ ]] pair 173 | remoteEval(`complete([=[${val}]=])`); 174 | }, 175 | "Up": function(cm) { 176 | let hpos = commandHistory.indexOf(cm.getValue()); 177 | if(hpos > 0) { 178 | cm.setValue(commandHistory[hpos-1]); 179 | } else if(hpos == 0) { 180 | // don't do anything 181 | } else { 182 | cm.setValue(commandHistory[commandHistory.length-1]); 183 | } 184 | }, 185 | "Down": function(cm) { 186 | let hpos = commandHistory.indexOf(cm.getValue()); 187 | if(hpos >= 0 && hpos < commandHistory.length - 1) { 188 | cm.setValue(commandHistory[hpos+1]); 189 | } 190 | } 191 | }); 192 | 193 | repl = new Terminal({ 194 | theme: { 195 | background: '#111' 196 | } 197 | }); 198 | fit = new FitAddon.FitAddon(); 199 | repl.loadAddon(fit); 200 | repl.open(document.getElementById("repl")); 201 | repl._initialized = true; 202 | 203 | fit.fit(); 204 | 205 | repl.writeln('Noita console'); 206 | repl.writeln('(Note that this panel is for output only)'); 207 | repl.writeln(''); 208 | } 209 | -------------------------------------------------------------------------------- /www/lib/codemirror.css: -------------------------------------------------------------------------------- 1 | /* BASICS */ 2 | 3 | .CodeMirror { 4 | /* Set height, width, borders, and global font properties here */ 5 | font-family: monospace; 6 | height: 100%; 7 | color: black; 8 | direction: ltr; 9 | } 10 | 11 | /* PADDING */ 12 | 13 | .CodeMirror-lines { 14 | padding: 4px 0; /* Vertical padding around content */ 15 | } 16 | .CodeMirror pre.CodeMirror-line, 17 | .CodeMirror pre.CodeMirror-line-like { 18 | padding: 0 4px; /* Horizontal padding of content */ 19 | } 20 | 21 | .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 22 | background-color: white; /* The little square between H and V scrollbars */ 23 | } 24 | 25 | /* GUTTER */ 26 | 27 | .CodeMirror-gutters { 28 | border-right: 1px solid #ddd; 29 | background-color: #f7f7f7; 30 | white-space: nowrap; 31 | } 32 | .CodeMirror-linenumbers {} 33 | .CodeMirror-linenumber { 34 | padding: 0 3px 0 5px; 35 | min-width: 20px; 36 | text-align: right; 37 | color: #999; 38 | white-space: nowrap; 39 | } 40 | 41 | .CodeMirror-guttermarker { color: black; } 42 | .CodeMirror-guttermarker-subtle { color: #999; } 43 | 44 | /* CURSOR */ 45 | 46 | .CodeMirror-cursor { 47 | border-left: 1px solid black; 48 | border-right: none; 49 | width: 0; 50 | } 51 | /* Shown when moving in bi-directional text */ 52 | .CodeMirror div.CodeMirror-secondarycursor { 53 | border-left: 1px solid silver; 54 | } 55 | .cm-fat-cursor .CodeMirror-cursor { 56 | width: auto; 57 | border: 0 !important; 58 | background: #7e7; 59 | } 60 | .cm-fat-cursor div.CodeMirror-cursors { 61 | z-index: 1; 62 | } 63 | .cm-fat-cursor-mark { 64 | background-color: rgba(20, 255, 20, 0.5); 65 | -webkit-animation: blink 1.06s steps(1) infinite; 66 | -moz-animation: blink 1.06s steps(1) infinite; 67 | animation: blink 1.06s steps(1) infinite; 68 | } 69 | .cm-animate-fat-cursor { 70 | width: auto; 71 | border: 0; 72 | -webkit-animation: blink 1.06s steps(1) infinite; 73 | -moz-animation: blink 1.06s steps(1) infinite; 74 | animation: blink 1.06s steps(1) infinite; 75 | background-color: #7e7; 76 | } 77 | @-moz-keyframes blink { 78 | 0% {} 79 | 50% { background-color: transparent; } 80 | 100% {} 81 | } 82 | @-webkit-keyframes blink { 83 | 0% {} 84 | 50% { background-color: transparent; } 85 | 100% {} 86 | } 87 | @keyframes blink { 88 | 0% {} 89 | 50% { background-color: transparent; } 90 | 100% {} 91 | } 92 | 93 | /* Can style cursor different in overwrite (non-insert) mode */ 94 | .CodeMirror-overwrite .CodeMirror-cursor {} 95 | 96 | .cm-tab { display: inline-block; text-decoration: inherit; } 97 | 98 | .CodeMirror-rulers { 99 | position: absolute; 100 | left: 0; right: 0; top: -50px; bottom: 0; 101 | overflow: hidden; 102 | } 103 | .CodeMirror-ruler { 104 | border-left: 1px solid #ccc; 105 | top: 0; bottom: 0; 106 | position: absolute; 107 | } 108 | 109 | /* DEFAULT THEME */ 110 | 111 | .cm-s-default .cm-header {color: blue;} 112 | .cm-s-default .cm-quote {color: #090;} 113 | .cm-negative {color: #d44;} 114 | .cm-positive {color: #292;} 115 | .cm-header, .cm-strong {font-weight: bold;} 116 | .cm-em {font-style: italic;} 117 | .cm-link {text-decoration: underline;} 118 | .cm-strikethrough {text-decoration: line-through;} 119 | 120 | .cm-s-default .cm-keyword {color: #708;} 121 | .cm-s-default .cm-atom {color: #219;} 122 | .cm-s-default .cm-number {color: #164;} 123 | .cm-s-default .cm-def {color: #00f;} 124 | .cm-s-default .cm-variable, 125 | .cm-s-default .cm-punctuation, 126 | .cm-s-default .cm-property, 127 | .cm-s-default .cm-operator {} 128 | .cm-s-default .cm-variable-2 {color: #05a;} 129 | .cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;} 130 | .cm-s-default .cm-comment {color: #a50;} 131 | .cm-s-default .cm-string {color: #a11;} 132 | .cm-s-default .cm-string-2 {color: #f50;} 133 | .cm-s-default .cm-meta {color: #555;} 134 | .cm-s-default .cm-qualifier {color: #555;} 135 | .cm-s-default .cm-builtin {color: #30a;} 136 | .cm-s-default .cm-bracket {color: #997;} 137 | .cm-s-default .cm-tag {color: #170;} 138 | .cm-s-default .cm-attribute {color: #00c;} 139 | .cm-s-default .cm-hr {color: #999;} 140 | .cm-s-default .cm-link {color: #00c;} 141 | 142 | .cm-s-default .cm-error {color: #f00;} 143 | .cm-invalidchar {color: #f00;} 144 | 145 | .CodeMirror-composing { border-bottom: 2px solid; } 146 | 147 | /* Default styles for common addons */ 148 | 149 | div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;} 150 | div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;} 151 | .CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); } 152 | .CodeMirror-activeline-background {background: #e8f2ff;} 153 | 154 | /* STOP */ 155 | 156 | /* The rest of this file contains styles related to the mechanics of 157 | the editor. You probably shouldn't touch them. */ 158 | 159 | .CodeMirror { 160 | position: relative; 161 | overflow: hidden; 162 | background: white; 163 | } 164 | 165 | .CodeMirror-scroll { 166 | overflow: scroll !important; /* Things will break if this is overridden */ 167 | /* 30px is the magic margin used to hide the element's real scrollbars */ 168 | /* See overflow: hidden in .CodeMirror */ 169 | margin-bottom: -30px; margin-right: -30px; 170 | padding-bottom: 30px; 171 | height: 100%; 172 | outline: none; /* Prevent dragging from highlighting the element */ 173 | position: relative; 174 | } 175 | .CodeMirror-sizer { 176 | position: relative; 177 | border-right: 30px solid transparent; 178 | } 179 | 180 | /* The fake, visible scrollbars. Used to force redraw during scrolling 181 | before actual scrolling happens, thus preventing shaking and 182 | flickering artifacts. */ 183 | .CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler { 184 | position: absolute; 185 | z-index: 6; 186 | display: none; 187 | } 188 | .CodeMirror-vscrollbar { 189 | right: 0; top: 0; 190 | overflow-x: hidden; 191 | overflow-y: scroll; 192 | } 193 | .CodeMirror-hscrollbar { 194 | bottom: 0; left: 0; 195 | overflow-y: hidden; 196 | overflow-x: scroll; 197 | } 198 | .CodeMirror-scrollbar-filler { 199 | right: 0; bottom: 0; 200 | } 201 | .CodeMirror-gutter-filler { 202 | left: 0; bottom: 0; 203 | } 204 | 205 | .CodeMirror-gutters { 206 | position: absolute; left: 0; top: 0; 207 | min-height: 100%; 208 | z-index: 3; 209 | } 210 | .CodeMirror-gutter { 211 | white-space: normal; 212 | height: 100%; 213 | display: inline-block; 214 | vertical-align: top; 215 | margin-bottom: -30px; 216 | } 217 | .CodeMirror-gutter-wrapper { 218 | position: absolute; 219 | z-index: 4; 220 | background: none !important; 221 | border: none !important; 222 | } 223 | .CodeMirror-gutter-background { 224 | position: absolute; 225 | top: 0; bottom: 0; 226 | z-index: 4; 227 | } 228 | .CodeMirror-gutter-elt { 229 | position: absolute; 230 | cursor: default; 231 | z-index: 4; 232 | } 233 | .CodeMirror-gutter-wrapper ::selection { background-color: transparent } 234 | .CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent } 235 | 236 | .CodeMirror-lines { 237 | cursor: text; 238 | min-height: 1px; /* prevents collapsing before first draw */ 239 | } 240 | .CodeMirror pre.CodeMirror-line, 241 | .CodeMirror pre.CodeMirror-line-like { 242 | /* Reset some styles that the rest of the page might have set */ 243 | -moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0; 244 | border-width: 0; 245 | background: transparent; 246 | font-family: inherit; 247 | font-size: inherit; 248 | margin: 0; 249 | white-space: pre; 250 | word-wrap: normal; 251 | line-height: inherit; 252 | color: inherit; 253 | z-index: 2; 254 | position: relative; 255 | overflow: visible; 256 | -webkit-tap-highlight-color: transparent; 257 | -webkit-font-variant-ligatures: contextual; 258 | font-variant-ligatures: contextual; 259 | } 260 | .CodeMirror-wrap pre.CodeMirror-line, 261 | .CodeMirror-wrap pre.CodeMirror-line-like { 262 | word-wrap: break-word; 263 | white-space: pre-wrap; 264 | word-break: normal; 265 | } 266 | 267 | .CodeMirror-linebackground { 268 | position: absolute; 269 | left: 0; right: 0; top: 0; bottom: 0; 270 | z-index: 0; 271 | } 272 | 273 | .CodeMirror-linewidget { 274 | position: relative; 275 | z-index: 2; 276 | padding: 0.1px; /* Force widget margins to stay inside of the container */ 277 | } 278 | 279 | .CodeMirror-widget {} 280 | 281 | .CodeMirror-rtl pre { direction: rtl; } 282 | 283 | .CodeMirror-code { 284 | outline: none; 285 | } 286 | 287 | /* Force content-box sizing for the elements where we expect it */ 288 | .CodeMirror-scroll, 289 | .CodeMirror-sizer, 290 | .CodeMirror-gutter, 291 | .CodeMirror-gutters, 292 | .CodeMirror-linenumber { 293 | -moz-box-sizing: content-box; 294 | box-sizing: content-box; 295 | } 296 | 297 | .CodeMirror-measure { 298 | position: absolute; 299 | width: 100%; 300 | height: 0; 301 | overflow: hidden; 302 | visibility: hidden; 303 | } 304 | 305 | .CodeMirror-cursor { 306 | position: absolute; 307 | pointer-events: none; 308 | } 309 | .CodeMirror-measure pre { position: static; } 310 | 311 | div.CodeMirror-cursors { 312 | visibility: hidden; 313 | position: relative; 314 | z-index: 3; 315 | } 316 | div.CodeMirror-dragcursors { 317 | visibility: visible; 318 | } 319 | 320 | .CodeMirror-focused div.CodeMirror-cursors { 321 | visibility: visible; 322 | } 323 | 324 | .CodeMirror-selected { background: #d9d9d9; } 325 | .CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; } 326 | .CodeMirror-crosshair { cursor: crosshair; } 327 | .CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; } 328 | .CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; } 329 | 330 | .cm-searching { 331 | background-color: #ffa; 332 | background-color: rgba(255, 255, 0, .4); 333 | } 334 | 335 | /* Used to force a border model for a node */ 336 | .cm-force-border { padding-right: .1px; } 337 | 338 | @media print { 339 | /* Hide the cursor when printing */ 340 | .CodeMirror div.CodeMirror-cursors { 341 | visibility: hidden; 342 | } 343 | } 344 | 345 | /* See issue #2901 */ 346 | .cm-tab-wrap-hack:after { content: ''; } 347 | 348 | /* Help users use markselection to safely style text background */ 349 | span.CodeMirror-selectedtext { background: none; } 350 | -------------------------------------------------------------------------------- /www/lib/modes/lua/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | CodeMirror: Lua mode 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 26 | 27 |
28 |

Lua mode

29 |
70 | 76 | 77 |

Loosely based on Franciszek 78 | Wawrzak's CodeMirror 79 | 1 mode. One configuration parameter is 80 | supported, specials, to which you can provide an 81 | array of strings to have those identifiers highlighted with 82 | the lua-special style.

83 |

MIME types defined: text/x-lua.

84 | 85 |
86 | -------------------------------------------------------------------------------- /www/lib/modes/lua/lua.js: -------------------------------------------------------------------------------- 1 | // CodeMirror, copyright (c) by Marijn Haverbeke and others 2 | // Distributed under an MIT license: https://codemirror.net/LICENSE 3 | 4 | // LUA mode. Ported to CodeMirror 2 from Franciszek Wawrzak's 5 | // CodeMirror 1 mode. 6 | // highlights keywords, strings, comments (no leveling supported! ("[==[")), tokens, basic indenting 7 | 8 | (function(mod) { 9 | if (typeof exports == "object" && typeof module == "object") // CommonJS 10 | mod(require("../../lib/codemirror")); 11 | else if (typeof define == "function" && define.amd) // AMD 12 | define(["../../lib/codemirror"], mod); 13 | else // Plain browser env 14 | mod(CodeMirror); 15 | })(function(CodeMirror) { 16 | "use strict"; 17 | 18 | CodeMirror.defineMode("lua", function(config, parserConfig) { 19 | var indentUnit = config.indentUnit; 20 | 21 | function prefixRE(words) { 22 | return new RegExp("^(?:" + words.join("|") + ")", "i"); 23 | } 24 | function wordRE(words) { 25 | return new RegExp("^(?:" + words.join("|") + ")$", "i"); 26 | } 27 | var specials = wordRE(parserConfig.specials || []); 28 | 29 | // long list of standard functions from lua manual 30 | var builtins = wordRE([ 31 | "_G","_VERSION","assert","collectgarbage","dofile","error","getfenv","getmetatable","ipairs","load", 32 | "loadfile","loadstring","module","next","pairs","pcall","print","rawequal","rawget","rawset","require", 33 | "select","setfenv","setmetatable","tonumber","tostring","type","unpack","xpcall", 34 | 35 | "coroutine.create","coroutine.resume","coroutine.running","coroutine.status","coroutine.wrap","coroutine.yield", 36 | 37 | "debug.debug","debug.getfenv","debug.gethook","debug.getinfo","debug.getlocal","debug.getmetatable", 38 | "debug.getregistry","debug.getupvalue","debug.setfenv","debug.sethook","debug.setlocal","debug.setmetatable", 39 | "debug.setupvalue","debug.traceback", 40 | 41 | "close","flush","lines","read","seek","setvbuf","write", 42 | 43 | "io.close","io.flush","io.input","io.lines","io.open","io.output","io.popen","io.read","io.stderr","io.stdin", 44 | "io.stdout","io.tmpfile","io.type","io.write", 45 | 46 | "math.abs","math.acos","math.asin","math.atan","math.atan2","math.ceil","math.cos","math.cosh","math.deg", 47 | "math.exp","math.floor","math.fmod","math.frexp","math.huge","math.ldexp","math.log","math.log10","math.max", 48 | "math.min","math.modf","math.pi","math.pow","math.rad","math.random","math.randomseed","math.sin","math.sinh", 49 | "math.sqrt","math.tan","math.tanh", 50 | 51 | "os.clock","os.date","os.difftime","os.execute","os.exit","os.getenv","os.remove","os.rename","os.setlocale", 52 | "os.time","os.tmpname", 53 | 54 | "package.cpath","package.loaded","package.loaders","package.loadlib","package.path","package.preload", 55 | "package.seeall", 56 | 57 | "string.byte","string.char","string.dump","string.find","string.format","string.gmatch","string.gsub", 58 | "string.len","string.lower","string.match","string.rep","string.reverse","string.sub","string.upper", 59 | 60 | "table.concat","table.insert","table.maxn","table.remove","table.sort" 61 | ]); 62 | var keywords = wordRE(["and","break","elseif","false","nil","not","or","return", 63 | "true","function", "end", "if", "then", "else", "do", 64 | "while", "repeat", "until", "for", "in", "local" ]); 65 | 66 | var indentTokens = wordRE(["function", "if","repeat","do", "\\(", "{"]); 67 | var dedentTokens = wordRE(["end", "until", "\\)", "}"]); 68 | var dedentPartial = prefixRE(["end", "until", "\\)", "}", "else", "elseif"]); 69 | 70 | function readBracket(stream) { 71 | var level = 0; 72 | while (stream.eat("=")) ++level; 73 | stream.eat("["); 74 | return level; 75 | } 76 | 77 | function normal(stream, state) { 78 | var ch = stream.next(); 79 | if (ch == "-" && stream.eat("-")) { 80 | if (stream.eat("[") && stream.eat("[")) 81 | return (state.cur = bracketed(readBracket(stream), "comment"))(stream, state); 82 | stream.skipToEnd(); 83 | return "comment"; 84 | } 85 | if (ch == "\"" || ch == "'") 86 | return (state.cur = string(ch))(stream, state); 87 | if (ch == "[" && /[\[=]/.test(stream.peek())) 88 | return (state.cur = bracketed(readBracket(stream), "string"))(stream, state); 89 | if (/\d/.test(ch)) { 90 | stream.eatWhile(/[\w.%]/); 91 | return "number"; 92 | } 93 | if (/[\w_]/.test(ch)) { 94 | stream.eatWhile(/[\w\\\-_.]/); 95 | return "variable"; 96 | } 97 | return null; 98 | } 99 | 100 | function bracketed(level, style) { 101 | return function(stream, state) { 102 | var curlev = null, ch; 103 | while ((ch = stream.next()) != null) { 104 | if (curlev == null) {if (ch == "]") curlev = 0;} 105 | else if (ch == "=") ++curlev; 106 | else if (ch == "]" && curlev == level) { state.cur = normal; break; } 107 | else curlev = null; 108 | } 109 | return style; 110 | }; 111 | } 112 | 113 | function string(quote) { 114 | return function(stream, state) { 115 | var escaped = false, ch; 116 | while ((ch = stream.next()) != null) { 117 | if (ch == quote && !escaped) break; 118 | escaped = !escaped && ch == "\\"; 119 | } 120 | if (!escaped) state.cur = normal; 121 | return "string"; 122 | }; 123 | } 124 | 125 | return { 126 | startState: function(basecol) { 127 | return {basecol: basecol || 0, indentDepth: 0, cur: normal}; 128 | }, 129 | 130 | token: function(stream, state) { 131 | if (stream.eatSpace()) return null; 132 | var style = state.cur(stream, state); 133 | var word = stream.current(); 134 | if (style == "variable") { 135 | if (keywords.test(word)) style = "keyword"; 136 | else if (builtins.test(word)) style = "builtin"; 137 | else if (specials.test(word)) style = "variable-2"; 138 | } 139 | if ((style != "comment") && (style != "string")){ 140 | if (indentTokens.test(word)) ++state.indentDepth; 141 | else if (dedentTokens.test(word)) --state.indentDepth; 142 | } 143 | return style; 144 | }, 145 | 146 | indent: function(state, textAfter) { 147 | var closing = dedentPartial.test(textAfter); 148 | return state.basecol + indentUnit * (state.indentDepth - (closing ? 1 : 0)); 149 | }, 150 | 151 | lineComment: "--", 152 | blockCommentStart: "--[[", 153 | blockCommentEnd: "]]" 154 | }; 155 | }); 156 | 157 | CodeMirror.defineMIME("text/x-lua", "lua"); 158 | 159 | }); 160 | -------------------------------------------------------------------------------- /www/lib/xterm-addon-fit.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FitAddon=t():e.FitAddon=t()}(window,function(){return function(e){var t={};function r(n){if(t[n])return t[n].exports;var o=t[n]={i:n,l:!1,exports:{}};return e[n].call(o.exports,o,o.exports,r),o.l=!0,o.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)r.d(n,o,function(t){return e[t]}.bind(null,o));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=0)}([function(e,t,r){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var n=function(){function e(){}return e.prototype.activate=function(e){this._terminal=e},e.prototype.dispose=function(){},e.prototype.fit=function(){var e=this.proposeDimensions();if(e&&this._terminal){var t=this._terminal._core;this._terminal.rows===e.rows&&this._terminal.cols===e.cols||(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}},e.prototype.proposeDimensions=function(){if(this._terminal&&this._terminal.element&&this._terminal.element.parentElement){var e=this._terminal._core,t=window.getComputedStyle(this._terminal.element.parentElement),r=parseInt(t.getPropertyValue("height")),n=Math.max(0,parseInt(t.getPropertyValue("width"))),o=window.getComputedStyle(this._terminal.element),i=r-(parseInt(o.getPropertyValue("padding-top"))+parseInt(o.getPropertyValue("padding-bottom"))),a=n-(parseInt(o.getPropertyValue("padding-right"))+parseInt(o.getPropertyValue("padding-left")))-e.viewport.scrollBarWidth;return{cols:Math.max(2,Math.floor(a/e._renderService.dimensions.actualCellWidth)),rows:Math.max(1,Math.floor(i/e._renderService.dimensions.actualCellHeight))}}},e}();t.FitAddon=n}])}); 2 | //# sourceMappingURL=xterm-addon-fit.js.map -------------------------------------------------------------------------------- /www/lib/xterm.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2014 The xterm.js authors. All rights reserved. 3 | * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) 4 | * https://github.com/chjj/term.js 5 | * @license MIT 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy 8 | * of this software and associated documentation files (the "Software"), to deal 9 | * in the Software without restriction, including without limitation the rights 10 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | * copies of the Software, and to permit persons to whom the Software is 12 | * furnished to do so, subject to the following conditions: 13 | * 14 | * The above copyright notice and this permission notice shall be included in 15 | * all copies or substantial portions of the Software. 16 | * 17 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 | * THE SOFTWARE. 24 | * 25 | * Originally forked from (with the author's permission): 26 | * Fabrice Bellard's javascript vt100 for jslinux: 27 | * http://bellard.org/jslinux/ 28 | * Copyright (c) 2011 Fabrice Bellard 29 | * The original design remains. The terminal itself 30 | * has been extended to include xterm CSI codes, among 31 | * other features. 32 | */ 33 | 34 | /** 35 | * Default styles for xterm.js 36 | */ 37 | 38 | .xterm { 39 | font-feature-settings: "liga" 0; 40 | position: relative; 41 | user-select: none; 42 | -ms-user-select: none; 43 | -webkit-user-select: none; 44 | } 45 | 46 | .xterm.focus, 47 | .xterm:focus { 48 | outline: none; 49 | } 50 | 51 | .xterm .xterm-helpers { 52 | position: absolute; 53 | top: 0; 54 | /** 55 | * The z-index of the helpers must be higher than the canvases in order for 56 | * IMEs to appear on top. 57 | */ 58 | z-index: 5; 59 | } 60 | 61 | .xterm .xterm-helper-textarea { 62 | /* 63 | * HACK: to fix IE's blinking cursor 64 | * Move textarea out of the screen to the far left, so that the cursor is not visible. 65 | */ 66 | position: absolute; 67 | opacity: 0; 68 | left: -9999em; 69 | top: 0; 70 | width: 0; 71 | height: 0; 72 | z-index: -5; 73 | /** Prevent wrapping so the IME appears against the textarea at the correct position */ 74 | white-space: nowrap; 75 | overflow: hidden; 76 | resize: none; 77 | } 78 | 79 | .xterm .composition-view { 80 | /* TODO: Composition position got messed up somewhere */ 81 | background: #000; 82 | color: #FFF; 83 | display: none; 84 | position: absolute; 85 | white-space: nowrap; 86 | z-index: 1; 87 | } 88 | 89 | .xterm .composition-view.active { 90 | display: block; 91 | } 92 | 93 | .xterm .xterm-viewport { 94 | /* On OS X this is required in order for the scroll bar to appear fully opaque */ 95 | background-color: #000; 96 | overflow-y: scroll; 97 | cursor: default; 98 | position: absolute; 99 | right: 0; 100 | left: 0; 101 | top: 0; 102 | bottom: 0; 103 | } 104 | 105 | .xterm .xterm-screen { 106 | position: relative; 107 | } 108 | 109 | .xterm .xterm-screen canvas { 110 | position: absolute; 111 | left: 0; 112 | top: 0; 113 | } 114 | 115 | .xterm .xterm-scroll-area { 116 | visibility: hidden; 117 | } 118 | 119 | .xterm-char-measure-element { 120 | display: inline-block; 121 | visibility: hidden; 122 | position: absolute; 123 | top: 0; 124 | left: -9999em; 125 | line-height: normal; 126 | } 127 | 128 | .xterm { 129 | cursor: text; 130 | } 131 | 132 | .xterm.enable-mouse-events { 133 | /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */ 134 | cursor: default; 135 | } 136 | 137 | .xterm.xterm-cursor-pointer { 138 | cursor: pointer; 139 | } 140 | 141 | .xterm.column-select.focus { 142 | /* Column selection mode */ 143 | cursor: crosshair; 144 | } 145 | 146 | .xterm .xterm-accessibility, 147 | .xterm .xterm-message { 148 | position: absolute; 149 | left: 0; 150 | top: 0; 151 | bottom: 0; 152 | right: 0; 153 | z-index: 10; 154 | color: transparent; 155 | } 156 | 157 | .xterm .live-region { 158 | position: absolute; 159 | left: -9999px; 160 | width: 1px; 161 | height: 1px; 162 | overflow: hidden; 163 | } 164 | 165 | .xterm-dim { 166 | opacity: 0.5; 167 | } 168 | 169 | .xterm-underline { 170 | text-decoration: underline; 171 | } 172 | --------------------------------------------------------------------------------