├── .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 | 
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 |
--------------------------------------------------------------------------------