This page is intended to document Ved's internals as well as possible. The page is still being worked on, and more things are being added over time. If you'd like to know more about anything specific in Ved that I haven't explained yet here, or not well enough (except for the easter eggs :P), feel free to ask! If you'd like more information on how to make plugins, check this page. The repository is here.
Last updated: Sunday 18 November 2018 21:05 (UTC) (this is the last edit date of the file)
First of all, the following source files are used in Ved:
Filename | Description |
---|---|
conf.lua | LÖVE's configuration file, controlling default window settings and loaded LÖVE modules. |
const.lua | Constants - Contains tile numbers for all tilesets, known scripting commands, music names, and other lookup tables. |
coordsdialog.lua | Contains code related to the little room coordinates input after hitting Q in the main editor. Before 1.4.0, this was part of dialog.lua. |
corefunc.lua | Contains a few functions that are used so early in loading (and/or are used on the crash screen), they must exist before things like plugins and the error handler are loaded. |
devstrings.lua | Used for defining new text strings during development of a new version, before putting them in all the language files. |
dialog.lua | Contains code related to dialog boxes. Before 1.4.0, this also contained code for, right click menus, scrollbars and VVVVVV-style text boxes. |
dialog_uses.lua | Contains callback functions and definitions of fields for dialogs, which are used as arguments for dialog.create(...) |
drawhelp.lua | Holds drawhelp(), called by love.draw() in state 15 (the help state). The help system is also used for level notes and the plugins list. |
drawlevelslist.lua | Holds drawlevelslist(), called by love.draw() in state 6 (the loading screen state). |
drawmaineditor.lua | Holds drawmaineditor(), called by love.draw() in state 1 (the main editor state). |
drawmap.lua | Holds drawmap(), called by love.draw() in state 12 (the map state). |
drawscripteditor.lua | Holds drawscripteditor(), called by love.draw() in state 3 (the script editor state). |
drawsearch.lua | Holds drawsearch(), called by love.draw() in state 11 (the search state). |
errorhandler.lua | Contains code for both the crash screen and the plugin error screen. |
filefunc_lin.lua | Contains functions necessary for accessing the VVVVVV levels and graphics folders on Linux. Also has a function for opening a URL with xdg-open in case LÖVE 0.9.0 is being used (where love.system.openURL(url) doesn't exist yet) |
filefunc_luv.lua | Contains fallback love.filesystem functions for accessing fallback levels and graphics folders if the operating system is something other than Windows, OS X or Linux. |
filefunc_mac.lua | Contains functions necessary for accessing the VVVVVV levels and graphics folders on Mac OS X. Also has a function for opening a URL with open in case LÖVE 0.9.0 is being used (where love.system.openURL(url) doesn't exist yet) |
filefunc_win.lua | Contains functions necessary for accessing the VVVVVV levels and graphics folders on Windows. Also has a function for opening a URL with start in case LÖVE 0.9.0 is being used (where love.system.openURL(url) doesn't exist yet) |
func.lua | Contains many functions, especially general-purpose ones and core Ved functions. |
helpfunc.lua | Contains certain functions related to (editing) level notes, and the rest of the help system. |
incompatmain.lua | If LÖVE 0.8 or lower is used, this is loaded from main.lua. It displays a message that outdated LÖVE is being used in all available languages. |
keyfunc.lua | Handles the shortcut that can be used in the help screen to make text editable. |
loadallmetadata.lua | Returns level metadata for the levels list from a different thread. |
loadconfig.lua | Handles anything related to the settings. |
love10compat.lua | Loaded only when LÖVE 0.10.0 or higher is used, and provides compatibility with those versions. Contains the new love.wheelmoved callback. |
love11compat.lua | Loaded only when LÖVE 11.0 or higher is used, and provides compatibility with those versions. For example, this hijacks color functions so they work with 0-255 instead of 0-1. |
main.lua | The first file that is loaded. Loads the fonts, sets a few basic variables, and loads plugins.lua, errorhandler.lua and, most importantly, main2.lua. |
main2.lua | Loads most other source files and assets, and contains pretty much all love.* callbacks. |
plugins.lua | Makes sure plugins and their file edits and hooks are loaded |
resizablebox.lua | Has a system for a box that can be resized by dragging borders with the mouse. Was formerly used for resizing script boxes, but it was glitchy so it's now unused. |
rightclickmenu.lua | Contains code related to right click menus. Before 1.4.0, this was part of dialog.lua. |
roomfunc.lua | Contains functions related to rooms in levels, tiles and such. |
scaling.lua | Hijacks/Decorates a couple of LÖVE functions to make scaling work perfectly |
scriptfunc.lua | Contains functions related to scripts. |
scrollbar.lua | Contains code related to scrollbars. Before 1.4.0, this was part of dialog.lua. |
searchfunc.lua | Contains functions related to searching levels. |
slider.lua | Used for the number control in the options screen, holds the function int_control |
updatecheck.lua | Checks what the latest version of Ved is via HTTP, and reports back. This is run inside a separate thread. |
vvvvvv_textbox.lua | Contains code related to VVVVVV-style text boxes. Before 1.4.0, this was part of dialog.lua. |
vvvvvvxml.lua | Loads and parses levels from .vvvvvv level files, and creates and saves them. Also has a function for "loading" a blank level. |
Ved uses state numbers to represent different screens, menus and interfaces. Blue state numbers are not normally used anymore, and/or are not normally accessible, and many of them are leftover testing states.
# | Description |
---|---|
-3 | Black screen |
-2 | tostate 6 |
-1 | Display error (expected: errormsg) |
0 | Temp main menu (enter state). Can be accessed in debug mode by pressing F12. |
1 | The editor (will expect things to have been loaded) |
2 | Syntax highlighting test |
3 | Scripting editor |
4 | Some XML testing |
5 | Filesystem testing |
6 | Listing of all files in the levels folder, and load a level from here (loading screen) |
7 | Display all sprites from sprites.png where you can get the number of the sprite you're hovering over |
8 | Ancient save screen (you can type in a name and press enter) |
9 | Dialog test, and right click menu test |
10 | List of scripts, and enter one to load |
11 | Search |
12 | Map |
13 | Options screen |
14 | Enemy picker preview |
15 | Help/Level notes/Plugins list |
16 | Scroll bar test |
17 | folderopendialog utility |
18 | Show undo/redo stacks |
19 | Flags list |
20 | Resizable box test |
21 | Display overlapping entities (may be a visible function later) (maybe doesn't work properly) |
22 | Load a script file in the 3DS format (lines separated by dollars) |
23 | Load a script file NOT in the 3DS format (lines separated by \r\n or \n) |
24 | Simple plugins list (already not used) |
25 | Syntax highlighting color settings |
26 | Font test |
27 | Display/Scale settings |
28 | Level stats |
29 | Plural forms test |
100 and further can be allocated by plugins (next paragraph) |
In Ved 1.1.4 and higher, plugins can allocate an amount of states for their own use, without using hardcoded state numbers, making it unnecessary to think of unique state numbers that won't interfere with any other plugins or future Ved updates. The following functions can be used:
For example, take a plugin called My First Plugin, which uses three states. Upon startup, like in hook love_load_start or love_load_end, the plugin calls allocate_states("my_1st_plug", 3). If this is the only plugin, or the first plugin to call allocate_states(), the allocated states will now, internally, be 100, 101 and 102. Let's say My First Plugin has three buttons to go to each of the allocated states. The first button, when clicked, would call to_astate("my_1st_plug", 0), the second would call to_astate("my_1st_plug", 1) and the third would call to_astate("my_1st_plug", 2). Hook love_draw_state, would contain something like this:
if in_astate("my_1st_plug", 0) then -- Insert drawing code for first state! statecaught = true elseif in_astate("my_1st_plug", 1) then -- Insert drawing code for second state! statecaught = true elseif in_astate("my_1st_plug", 2) then -- Insert drawing code for third state! statecaught = true end
The hook func_loadstate could contain something similar for initialization code for all the states (but without statecaught = true)
The identifying name can be anything, but this name should be unique to one plugin. It's also possible to allocate multiple blocks of state numbers within the same plugin, if you use different names. If your plugin only has one state, you can leave out the number (allocate_states("my_1st_plug"), in_astate("my_1st_plug"), to_astate("my_1st_plug")). And of course, this means you can have multiple states that are only referred to by string names (I can see how in_astate("my_1st_plug_menu") and in_astate("my_1st_plug_display") can be more pleasing than in_astate("my_1st_plug", 0) and in_astate("my_1st_plug", 1)). It's up to you to choose whatever you like most, or whatever works best for your plugin.
Ved is compatible with all revisions of LÖVE 0.9.x, 0.10.x and 11.x, but its code is written for 0.9.x. Compatibility with newer versions is mostly achieved by causing update changes to be undone; for example, LÖVE functions that were renamed or expect different arguments are redefined/hijacked and then called by those redefinitions if arguments or return values need to be passed differently, and callbacks that get "new-style" data from LÖVE get a bit of conversion code at the top. There are a few instances of conditionals depending on the version number in regular code, but that is not very common.
Debug mode is a special mode used to access certain features and information that can be useful for debugging and developing Ved. Enabling debug mode has the following effects:
Debug mode is enabled by the boolean variable allowdebug. It is possible to enable it in-app by going from the load screen to the Ved options, clicking and holding the OK button, and "dragging" over the Send Feedback button holding the right mouse button as well.
metadata["Creator"] metadata["Title"] metadata["Created"] metadata["Modified"] metadata["Modifiers"] metadata["Desc1"] metadata["Desc2"] metadata["Desc3"] metadata["website"] metadata["mapwidth"] metadata["mapheight"] metadata["levmusic"]
{ x = 12, y = 34, t = 17, p1 = 0, p2 = 0, p3 = 0, p4 = 0, p5 = 320, p6 = 240, data = "Roomtext or script name" }
{ tileset = 0, tilecol = 0, platx1 = 0, platy1 = 0, platx2 = 320, platy2 = 240, platv = 4, enemyx1 = 0, enemyy1 = 0, enemyx2 = 320, enemyy2 = 240, enemytype = 0, directmode = 0, warpdir = 0, roomname = "Roomname", auto2mode = 0, }directmode is always present, even after a VVVVVV 2.0 level is loaded. If auto2mode == 1 then multi-tileset mode is used for that room, and in that case directmode should be 0. However, when saving, directmode is set to 1 in the level file because auto2mode is not saved to it.
scriptnames = { [1] = "mynewscript", [2] = "mynewscript_load" } scripts = { mynewscript_load = { [1] = "ifflag(2,stop)", [2] = "flag(2,on)", [3] = "iftrinkets(0,mynewscript)" }, mynewscript = { [1] = "reply(3)", [2] = "I probably don't really need it,", [3] = "but it might be nice to take it", [4] = "back to the ship to study..." } }If you're wondering how Ved stores internal scripts: say(x) #v is put at the start of the script, and loadscript(stop) #v and text(1,0,0,4) #v at the end (with additional text(1,0,0,4) #v + say(x) #v in between blocks if they have to be split.) If you want to check, hold down the shift key while opening a script. The same goes for checking checking flag names - Ved converts them to numbers when leaving the script editor and converts them back into names when opening it, unless you hold shift while opening.
local mycount = {trinkets = 0, crewmates = 0, entities = 0, startpoint = nil, FC = 0} -- FC = Failed Checks(mycount is local to that function, and will be returned and then stored to count. So this whole system of creating tables locally and then returning references to them works? Yeah, it does, funny eh? I should probably rewrite that a little bit though.)
The metadata entity is used by Ved to store data that is specific to a certain level. It is a roomtext entity at x=800 y=600, which means it's in room 21,21 (1-indexed). The data is formatted with several levels of separation characters:
| | primary separator (see this as separating different "tables" of data) |
$ | secondary separator (see this as separating different "rows" or "records" inside a "table") |
@ | tertiary separator (see this as separating different "columns" or "properties" inside a "rows"/"records") |
For example, one of the "tables" contains level notes. Each level note is separated by $, and inside a level note, the name and the contents of the note are separated by @.
Accent grave (`) is the escape character; if the real versions of certain characters need to be represented, they are escaped as follows:
` | `g |
| | `p |
$ | `d |
@ | `a |
(newline) | `n |
(2 spaces) | `_ |
The function despecialchars(text) encodes the raw characters to escaped format, undespecialchars(text) decodes escaped characters back to raw characters.
As said, | is the primary separator, which separates the following items:
Description | ≥V | |
---|---|---|
1 | Metadata entity version number | 0? |
2 | Flag names | 2 |
3 | (Reserved) | - |
4 | Level variables, for use by Ved or plugins (Why haven't I documented this out of commit messages) | 3 |
5 | Level notes | 0? |
(more coming soon)
Currently, when you copy a room to the clipboard, it's stored in a comma-separated format. It consists of 1215 values separated by commas, and it's structured as follows (indices start at 1, as in Lua):
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 — 1215 |
Tileset | Tilecol | Platf. bounds x1,y1,x2,y2 | Platv | Enemy bounds x1,y1,x2,y2 | Enemy type | Direct mode | Warp dir | Room name* | 1200 tile numbers |
In the room name, commas are replaced by the character ´ (U+00B4 ACUTE ACCENT).
In this format, entities are not copied. I'm planning to replace this CSV system by an XML format, which will also contain entities. Data in the old format will still be pasteable, and I'll probably also leave in a way to copy as CSV.
Strictly speaking, the buttons parameter accepts a list/table of buttons. For each element, if that element is a string, that string will be displayed as-is. But if possible, it should be an integer, representing one of the built-in buttons. A list of buttons is available as DB, so for example, DB.YES is a "Yes" button. The list is as follows:
Constant | Value | Button label |
---|---|---|
DB.OK | 1 | OK |
DB.CANCEL | 2 | Cancel |
DB.YES | 3 | Yes |
DB.NO | 4 | No |
DB.APPLY | 5 | Apply |
DB.QUIT | 6 | Quit |
DB.DISCARD | 7 | Discard |
DB.SAVE | 8 | Save |
DB.CLOSE | 9 | Close |
There's also built-in lists of buttons available as DBS, like DBS.YESNO, which stands for {DB.YES, DB.NO}, meaning a Yes and No button.
Constant | Buttons |
---|---|
DBS.OK | OK |
DBS.QUIT | Quit |
DBS.YESNO | Yes, No |
DBS.OKCANCEL | OK, Cancel |
DBS.OKCANCELAPPLY | OK, Cancel, Apply |
DBS.SAVEDISCARDCANCEL | Save, Discard, Cancel |
DBS.YESNOCANCEL | Yes, No, Cancel |
The purpose of the handler function is to take action after closing a dialog. For example, if a question is asked whether the user wants to destroy something, then that should be done if (and only if) the user chooses DB.YES.
The handler is a function that can take up to four arguments:
Dialog handlers as used in Ved's code can be found in the file dialog_uses.lua, starting with dialog.callback. An example handler is the following. Assume the buttons for this dialog are DBS.YESNOCANCEL. In this example, users press Yes if they want a new dialog to be created showing what they entered in a field with the key name, press No if they still want a dialog but don't want to know their input, and press Cancel if they want no new dialog.
function(button, fields) if button == DB.YES then dialog.create("You pressed Yes, and the input with key \"name\" is " .. fields.name) elseif button == DB.NO then dialog.create("You pressed No! Apparently you don't want to know what you entered, but you still want a dialog.") end endDB.CANCEL is not checked, therefore the handler does nothing if that button is pressed.
No-close checkers are similar to handlers, but they are called before the dialog closes. This function can stop the dialog from being closed by returning true, and thus are useful for creating error messages if the user puts in invalid input. They can also be used to not close the dialog if an Apply button is pressed, for example.
The arguments for the no-close checker are the same as for the main handler, except the fourth argument doesn't exist. If the no-close checker returns true (and thus stops the dialog from closing) the handler will still be called, and its fourth argument will be set to true as well. Here's an example of how a no-close checker can be used to give an error message in case someone enters a value for the field with key inp that is empty or above 20 characters:
function(button, fields) if button == DB.OK and (fields.inp == "" or fields.inp:len() > 20) then dialog.create("Your input must not be empty or longer than 20 characters.") return true end endNow the handler might be defined as follows:
function(button, fields, identifier, notclosed) if notclosed then return end if button == DB.OK then -- Save your fields.inp here. end endYou can find more examples in dialog_uses.lua in Ved, no-close checkers are generally prefixed _validate.
The X and Y positions for the field both start at 0, which is the position the regular dialog text also starts.
These are the different types of input fields:
0 | Text input (this is the default) |
1 | Dropdown |
2 | Plain text label (does not take input) |
3 | Checkbox |
More information about how the different types work:
Basically, there's two forms: first the simpler one. In the simpler form, you only need a list of items that will appear in the dropdown, and whenever the user selects an item, the value of the input field is set to the text of the option that the user selected. This means what's readable as an option will be passed. You may want to set the default value to an option in the list.
An example: {"drop", 0, 0, 30, "Option A", 1, {"Option A", "Option B", "Option C"}}
The width is set to 30 because that's how wide dropdown menus are (currently). If you want a function to be called every time an option is selected in this case, there'd be two more arguments: false (as a filler for the second table) and then the function.
For the second form, let's take the example of a user selecting between percentages, let's say 50%, 100% and 200%. You want to pass this as a number instead, so if the user selects 50%, you want the actual value to be 0.5. When the user does select 50%, the "onchange" function is called, and converts the "50%" into 0.5, and returns that. The second table that was mentioned (the one that converts a value to a displayable "current selection") has this 0.5 background value as a key, and that maps to a value of "50%".
So this is that example:
{ "drop2", 0, 2, 30, "1", 1, {"50%", "100%", "200%"}, {[0.5] = "50%", [1] = "100%", [2] = "200%"}, function(picked) if picked == "50%" then return 0.5 elseif picked == "100%" then return 1 elseif picked == "200%" then return 2 end end }You might want to generate tables for this in some way instead of hardcoding them like that, and let the function iterate over the table or index one or something, to make it a bit more elegant.
This is just a bit of text that can be displayed anywhere in the dialog you want. It can therefore be used to label other input fields without having to include those labels in the dialog contents.
The "default value" will be used as text, but it can also be a function that returns the text dynamically.
An example for a plain-text label is as follows: {"", 0, 5, 10, "Label", 2}
The key is left empty, because it has not much use. But we can't set it to nil, otherwise Lua might think the table ends there. It is positioned on the start of the 6th line. The width is 10 characters, which means it will merely wrap beyond that point. Then the label is just a string, and will be displayed. 2 is the type.
Here's an example of a label that keeps changing: {"", 0, 5, 40, function() return love.math.random() end, 2}
This will continuously display a different random number between 0 and 1. Note that the key and the table of fields are passed to the function, but it's not used here.
{"option", 0, 5, 2+font8:getWidth(OPTIONLABEL), true, 3}, {"", 2, 5, 40, OPTIONLABEL, 2}The default state of this checkbox is checked, since the default value is set to true here. It is followed by a plain text label (type 2), and the label can be clicked as well to toggle the checkbox.
The fourth argument of dialog.new (called buttons above) can be set to one of the following values V:
V | Button 3 | Button 2 | Button 1 |
---|---|---|---|
0 | |||
1 | OK | ||
2 | Quit | ||
3 | Yes | No | |
4 | OK | Cancel | |
5 | OK | Cancel | Apply |
6 | Save | Discard | Cancel |
(6 has been added in Ved 1.3.0)
General text input can be started by a single call to startinput(). There's also a function startinputonce(), which can be used if you decide to do it in update/drawing code, but expect me to remove that sooner or later. Input can be stopped (or locked) by calling stopinput(). The text input to the left of the cursor can be found in input, and text to the right of the cursor can be found in input_r. The variable __ (two underscores) contains the text cursor and the text to the right of it. So, to display the input field, you can concatenate input and __ (input .. __).
It's also possible to include text boxes in dialogs, see the above part on fields in dialogs.