Don't change these things! There's got to be enough intresting stuff for lga's intresting museums :-)sorcerykid wrote: A player on my sever recently notified me that the screwdriver can be used to rotate chests sideways. So I wrote a command-line Lua script to locate all such incorrectly oriented chests, furnaces, bookshelves, and vessels shelves within the world.
Post your code!
-
- Member
- Posts: 4290
- Joined: Sun Sep 09, 2012 17:31
- GitHub: Sokomine
- IRC: Sokomine
- In-game: Sokomine
Re: Post your code!
A list of my mods can be found here.
- sorcerykid
- Member
- Posts: 1847
- Joined: Fri Aug 26, 2016 15:36
- GitHub: sorcerykid
- In-game: Nemo
- Location: Illinois, USA
Re: Post your code!
I developed a raycast function for efficiently computing line of sight between two points in 3d space. It is loosely based on the nearest neighbor line-drawing algorithm, and thus focuses on speed rather than accuracy.
raytrace_simple( pos1, pos2, on_plot )
Using the function above, a more flexible alternative to minetest.line_of_sight() can be devised:
Notice that the return value of raytrace_simple() reflects whether the callback succeeded or failed while traversing the ray.
raytrace_simple( pos1, pos2, on_plot )
- pos1 - the starting position of the ray as a vector
- pos2 - the ending position of the ray as a vector
- on_plot - the callback function for traversing the ray
Code: Select all
local function raytrace_simple( pos1, pos2, on_plot )
local p1 = vector.round( pos1 )
local p2 = vector.round( pos2 )
local ax = abs( p2.x - p1.x )
local ay = abs( p2.y - p1.y )
local az = abs( p2.z - p1.z )
local step = max( ax, ay, az )
local dx = ( p2.x - p1.x ) / step;
local dy = ( p2.y - p1.y ) / step;
local dz = ( p2.z - p1.z ) / step;
local x = p1.x
local y = p1.y
local z = p1.z
for i = 1, step + 1 do
if not on_plot( i, { x = floor( x + 0.5 ), y = floor( y + 0.5 ), z = floor( z + 0.5 ) } ) then
return false
end
x = x + dx
y = y + dy
z = z + dz
end
return true
end
Code: Select all
line_of_sight = function ( source_pos, target_pos )
local unknown_ndef = { groups = { }, walkable = false }
return raytrace_simple( source_pos, target_pos, function ( idx, pos )
local node = minetest.get_node( pos )
local ndef = core.registered_nodes[ node.name ] or unknown_ndef -- account for unknown nodes
local is_airlike = not ndef.walkable
return is_airlike
end )
end
- sorcerykid
- Member
- Posts: 1847
- Joined: Fri Aug 26, 2016 15:36
- GitHub: sorcerykid
- In-game: Nemo
- Location: Illinois, USA
Re: Post your code!
This is a very simple Auth Redux ruleset for limiting new accounts to certain times of day, unless the server operator is online. Of course the range of hours can be changed by editing the pattern /8^19:?/t accordingly.
Code: Select all
try "Our server is only open to new players between 8:00-19:59 or when $owner is online"
fail all
if $is_new eq $true
unless $clock is /8^19:?/t
unless $owner in $users_list
continue
- Linuxdirk
- Member
- Posts: 3219
- Joined: Wed Sep 17, 2014 11:21
- In-game: Linuxdirk
- Location: Germany
- Contact:
Re: Post your code!
A function to get a configuration value as string from either ./minetest.conf, a world-specific ./worlds/worldname/modname.conf or a given default value.
It is also possible to mark values as not changeable via configuration options (which could be done simply by not using the function then, but it’s all about options, right?).
Simply add this to your configuration Lua file or add it to your mod’s global table’s function section (fits within the 80 characters rule, of course).
And use it in your code like this:
This automatically allows users to configure things to their liking using the following parameters (assuming mymod is your mod’s name).
If you later decide to have the third option also being configurable, simply remove the false parameter and players can set mymod_and_so_on, too.
It is also possible to mark values as not changeable via configuration options (which could be done simply by not using the function then, but it’s all about options, right?).
Simply add this to your configuration Lua file or add it to your mod’s global table’s function section (fits within the 80 characters rule, of course).
Code: Select all
local get_config_option = function (key_name, default_value, changeable)
local mod = minetest.get_current_modname()
local config_option = mod..'_'..key_name
local value = default_value
if changeable ~= false then
local wc = Settings(minetest.get_worldpath()..DIR_DELIM..mod..'.conf')
local global_setting = minetest.settings:get(config_option)
local world_setting = wc:get(config_option)
value = world_setting or global_setting or default_value or ''
end
return value
end
Code: Select all
local my_option = get_config_option('my_option', 'foo')
local my_other_option = get_config_option('my_other_option', 'bar')
local and_so_on = get_config_option('and_so_on', 'baz', false)
Code: Select all
mymod_my_option = foo
mymod_my_other_option = bar
- sorcerykid
- Member
- Posts: 1847
- Joined: Fri Aug 26, 2016 15:36
- GitHub: sorcerykid
- In-game: Nemo
- Location: Illinois, USA
Re: Post your code!
Here is a new Minetest API function to make including libraries and submodules easier at long last.
Before:
local helpers = dofile( minetest.get_modpath( "othermod" ) .. "/helpers.lua" )
After:
local helpers = minetest.include( "/othermod/helpers.lua" )
Before:
loadfile( minetest.get_modpath( "thismod" ) .. "/gui.lua" )( config, player_list )
After:
minetest.include( "gui.lua", config, player_list )
The minetest.include() function includes a file. It accepts a file specifier, which may be either a path relative to the currently loading mod or else relative to another mod with a leading slash. Any additional parameters are passed directly to the loaded script, which may be retrieved via local var1, var2, var3 = ... at the head of the included file.
Before:
local helpers = dofile( minetest.get_modpath( "othermod" ) .. "/helpers.lua" )
After:
local helpers = minetest.include( "/othermod/helpers.lua" )
Before:
loadfile( minetest.get_modpath( "thismod" ) .. "/gui.lua" )( config, player_list )
After:
minetest.include( "gui.lua", config, player_list )
The minetest.include() function includes a file. It accepts a file specifier, which may be either a path relative to the currently loading mod or else relative to another mod with a leading slash. Any additional parameters are passed directly to the loaded script, which may be retrieved via local var1, var2, var3 = ... at the head of the included file.
Code: Select all
function minetest.include( filespec, ... )
local mod_name, file_name = string.match( filespec, "^[/\](.-)[/\](.+)" )
if not mod_name then
mod_name = minetest.get_current_modname( )
file_name = filespec
end
return loadfile( minetest.get_modpath( mod_name ) .. "/" .. file_name )( ... )
end
- sorcerykid
- Member
- Posts: 1847
- Joined: Fri Aug 26, 2016 15:36
- GitHub: sorcerykid
- In-game: Nemo
- Location: Illinois, USA
Re: Post your code!
This is a handy command-line script for locating all "broken" beds in the world. It will output a list of bed parts that either
- consist of only a top but no bottom,
- consist of only a bottom but no top, or
- have incongruent orientation of bottom and top
- source_path = full path of the map.sqlite database to be opened in read-only mode
- search_area = the mapblock boundaries in which to limit the search (comment out to search the entire map)
Code: Select all
-- Find all mismatched bed parts (top or bottom)
package.path = "/home/minetest/maplib/?.lua;" .. package.path
local maplib = require "maplib"
local source_path = "/home/minetest/.minetest/worlds/world/map.sqlite"
--local search_area = MapArea( { x = -10, y = 0, z = -10 }, { x = 10, y = 0, z = 10 } )
---------------------------------------
local decode_node_pos = maplib.decode_node_pos
local hash_node_pos = maplib.hash_node_pos
local unhash_node_pos = maplib.unhash_node_pos
local pos_to_string = maplib.pos_to_string
local lower_parts = { }
local upper_parts = { }
local lower_count = 0
local upper_count = 0
local function analyze_block( block, index, node_list, nodename_map )
local is_found = false
for node_idx, node in ipairs( node_list ) do
local node_name = nodename_map[ node.id ]
if node_name == "beds:bed_bottom" or node_name == "beds:fancy_bed_bottom" then
local node_pos = decode_node_pos( node_idx, index )
local node_hash = hash_node_pos( node_pos )
lower_parts[ node_hash ] = node.param2
lower_count = lower_count + 1
elseif node_name == "beds:bed_top" or node_name == "beds:fancy_bed_top" then
local node_pos = decode_node_pos( node_idx, index )
local node_hash = hash_node_pos( node_pos )
upper_parts[ node_hash ] = node.param2
upper_count = upper_count + 1
end
end
end
local function compare_parts( )
for hash, param2 in pairs( lower_parts ) do
local pos2 = unhash_node_pos( hash )
if param2 == 0 then
pos2.z = pos2.z + 1
elseif param2 == 1 then
pos2.x = pos2.x + 1
elseif param2 == 2 then
pos2.z = pos2.z - 1
elseif param2 == 3 then
pos2.x = pos2.x - 1
else
pos2 = nil
end
if pos2 then
local hash2 = hash_node_pos( pos2 )
if upper_parts[ hash2 ] == param2 then
lower_parts[ hash ] = nil
upper_parts[ hash2 ] = nil
end
end
end
print( string.format( "Mismatched 'beds:bed_bottom' and 'beds:fancy_bed_bottom' (%d total):", lower_count ) )
for hash in pairs( lower_parts ) do
local pos = unhash_node_pos( hash )
print( string.format( "Found at %s", pos_to_string( pos ) ) )
end
print( string.format( "Mismatched 'beds:bed_top' and 'beds:fancy_bed_top' (%d total):", upper_count ) )
for hash in pairs( upper_parts ) do
local pos = unhash_node_pos( hash )
print( string.format( "Found at %s", pos_to_string( pos ) ) )
end
end
-----------------------------------------
local map_db = MapDatabase( source_path, false )
if search_area then
print( "Creating cache..." )
map_db.create_cache( false )
end
print( "Examining database..." )
local iterator = search_area and map_db.iterate_area( search_area ) or map_db.iterate( )
for index, block in iterator do
local nodename_map = block.nodename_map
for i, n in pairs( block.nodename_map ) do
-- if there is at least one bed, then analyze mapblock
if n == "beds:bed_bottom" or n == "beds:bed_top" or n == "beds:fancy_bed_bottom" or n == "beds:fancy_bed_top" then
local node_list = block.get_node_list( )
analyze_block( block, index, node_list, nodename_map )
break
end
end
end
compare_parts( )
Re: Post your code!
Minetest implementation of Midpoint Circle algorithm
Code: Select all
local function circle(pos,r,plane,callback)
local tpos = {}
tpos[plane] = pos[plane]
local ax = {'x','y','z'}
for i,e in ipairs(ax) do
if e==plane then
table.remove(ax,i)
if #ax ~= 2 then
minetest.log("error","circle: bad plane argument")
return
end
end
end
local dx, dy, err = r, 0, 1-r
local c1, c2 = ax[1], ax[2]
while dx >= dy do
tpos[c1], tpos[c2] = pos[c1]+dx, pos[c2]+dy
callback(tpos)
tpos[c1], tpos[c2] = pos[c1]-dx, pos[c2]+dy
callback(tpos)
tpos[c1], tpos[c2] = pos[c1]+dx, pos[c2]-dy
callback(tpos)
tpos[c1], tpos[c2] = pos[c1]-dx, pos[c2]-dy
callback(tpos)
tpos[c1], tpos[c2] = pos[c1]+dy, pos[c2]+dx
callback(tpos)
tpos[c1], tpos[c2] = pos[c1]-dy, pos[c2]+dx
callback(tpos)
tpos[c1], tpos[c2] = pos[c1]-dy, pos[c2]+dx
callback(tpos)
tpos[c1], tpos[c2] = pos[c1]+dy, pos[c2]-dx
callback(tpos)
tpos[c1], tpos[c2] = pos[c1]-dy, pos[c2]-dx
callback(tpos)
dy = dy + 1
if err < 0 then
err = err + 2 * dy + 1
else
dx, err = dx-1, err + 2 * (dy - dx) + 1
end
end
end
- Attachments
-
- screenshot_20210112_104151.png (93.21 KiB) Viewed 6643 times
- AspireMint
- Member
- Posts: 415
- Joined: Mon Jul 09, 2012 12:59
- GitHub: AspireMint
- IRC: AspireMint
- In-game: AspireMint
- Location: Stuck at spawn
Re: Post your code!
Code: Select all
wrap = function(t)
local w = {}
setmetatable(w, { __index = t })
return w
end
unwrap = function(w)
local mt = getmetatable(w)
return mt and mt.__index or w
end
Example:
Code: Select all
local M = wrap(minetest)
M.chat_send_all("yay A")
M.chat_send_all = function(msg)
print("shhh")
minetest.chat_send_all(msg)
-- or unwrap(M).chat_send_all(msg)
end
M.chat_send_all("yay two")
minetest.chat_send_all("yay 3")
Re: Post your code!
SQL query for sqlite databases that returns all mapblocks in a specified volume, corrects for off by -1 errors that can occur when extracting x, y and z via bit manipulation. i.e. if x > 2048 value for y would be 1 less than correct value, likewise if the corrected value for y > 2048 the value for z would be 1 less than its correct value.
Edit: Added Python code to dehash pos using bit manipulation:
Code: Select all
SELECT pos,
data
FROM (
SELECT pos,
CASE
WHEN (pos & 0xfff) > 2048
THEN (pos & 0xfff) - 4096
ELSE pos & 0xfff
END AS x,
CASE
WHEN (((pos & 0xfff000) >> 12) + ((pos & 0xfff) > 2048)) > 2048
THEN ((pos & 0xfff000) >> 12) + ((pos & 0xfff) > 2048) - 4096
ELSE ((pos & 0xfff000) >> 12) + ((pos & 0xfff) > 2048)
END AS y,
CASE
WHEN (((pos & 0xfff000) >> 12) + ((pos & 0xfff) > 2048)) > 2048
THEN
CASE
WHEN (((pos & 0xfff000000) >> 24) + 1) > 2048
THEN (((pos & 0xfff000000) >> 24) + 1) - 4096
ELSE (((pos & 0xfff000000) >> 24) + 1)
END
ELSE
CASE
WHEN ((pos & 0xfff000000) >> 24) > 2048
THEN ((pos & 0xfff000000) >> 24) - 4096
ELSE ((pos & 0xfff000000) >> 24)
END
END AS z,
data
FROM blocks
)
WHERE x BETWEEN -2 AND 2 AND
y BETWEEN -2 AND 2 AND
z BETWEEN -2 AND 2
Edit: Added Python code to dehash pos using bit manipulation:
Code: Select all
def getIntegerAsBlock(pos):
x = pos & 4095
y = ((pos & 16773120) >> 12)
z = ((pos & 68702699520) >> 24)
if x > 2048:
x -= 4096
y += 1
if y > 2048:
y -= 4096
z += 1
if z > 2048:
z -= 4096
return x, y, z
- sorcerykid
- Member
- Posts: 1847
- Joined: Fri Aug 26, 2016 15:36
- GitHub: sorcerykid
- In-game: Nemo
- Location: Illinois, USA
Re: Post your code!
Here is a helper function for obtaining the daypart as a string, useful for ensuring that certain events only occur at specific periods of the day; e.g. having a cicada sound only play during the evening hours.
The function returns a table with two key/value pairs: quarter and half. Both are fairly self-explanatory. For example, when the time of day is 13:15, the result will be { quarter = "afternoon", half = "daytime" }.
Code: Select all
function to_daypart( time )
-- by half:
-- * 6am - 6pm (daytime)
-- * 6pm - 6am (nighttime)
-- by quarter:
-- * 6am - noon (morning)
-- * noon - 6pm (afternoon)
-- * 6pm - midnight (evening)
-- * midnight - 6am (overnight)
local quarter, half
if time == nil then time = minetest.get_timeofday( ) end
if time < 0.25 then
quarter = "overnight"
elseif time < 0.5 then
quarter = "morning"
elseif time < 0.75 then
quarter = "afternoon"
else
quarter = "evening"
end
if time < 0.25 or time > 0.75 then
half = "nighttime"
else
half = "daytime"
end
return { quarter = quarter, half = half }
end
- sorcerykid
- Member
- Posts: 1847
- Joined: Fri Aug 26, 2016 15:36
- GitHub: sorcerykid
- In-game: Nemo
- Location: Illinois, USA
Re: Post your code!
Here is a much improved version of the string matching helper function that I posted back in 2019. Unlike the previous implementation, this one does not pollute the global environment. And the capture groups are stored in a dedicated _C variable, rather than recycling the commonly use underscore variable.
Notice that string.is_match() is now a factory. It returns the actual string-matching function along with the table that will be used to store the capture groups. Of course, these variables should be lexically scoped (that is, local to the mod itself or to the current block).
local is_match, _C = string.is_match()
This is an example of how it can be used in a scenario where you want to accept input either in the form of [distance] [axis] or else [xpos] [ypos] [zpos], where numbers can be decimals or floating points.
Code: Select all
function string.is_match()
local _C = { }
return function ( text, pattern )
local res = { string.match( text, pattern ) }
setmetatable( _C, { __index = res } )
return #res > 0
end, _C
end
local is_match, _C = string.is_match()
This is an example of how it can be used in a scenario where you want to accept input either in the form of [distance] [axis] or else [xpos] [ypos] [zpos], where numbers can be decimals or floating points.
Code: Select all
local input = "3 X"
local is_match, _C = string.is_match( )
local function move_by( a, b )
print( a, b )
end
local function move_to( a, b, c )
print( a, b, c )
end
if is_match( "(-?%d) ([XxYyZz])" ) or is_match"(-?%d%.%d) ([XxYyZz])" ) then
move_by( string.lower( _C[ 2 ] ), tonumber( _C[ 1 ] ) )
elseif is_match( "(-?%d+)[, ](-?%d+)[, ](-?%d+)" ) or
is_match( "(-?%d%.%d+)[, ](-?%d%.%d+)[, ](-?%d%.%d+)" ) then
move_to( tonumber( _C[ 1 ] ), tonumber( _C[ 2 ] ), tonumber( _C[ 3 ] ) )
else
print( "Invalid input!" )
end
- sorcerykid
- Member
- Posts: 1847
- Joined: Fri Aug 26, 2016 15:36
- GitHub: sorcerykid
- In-game: Nemo
- Location: Illinois, USA
Re: Post your code!
The following Lua helper function can be used to split the lines of a given input string (such as from a plain text file), into a table. Optionally, blank lines can be suppressed by passing true for the second parameter.
lines = splitlines( str, has_blanks )
I ended up writing this function because the solutions I found online failed to handle blank lines correctly. Some would treat a trailing newline character as a blank line, even though that's just the EOF. Others would interpret an empty string as a single line, even though it's really no lines. Still others would completely "collapse" any blank lines, rather than preserving them as individual empty strings.
For example: https://stackoverflow.com/questions/328 ... ing-gmatch
Here are the test cases:
lines = splitlines( str, has_blanks )
I ended up writing this function because the solutions I found online failed to handle blank lines correctly. Some would treat a trailing newline character as a blank line, even though that's just the EOF. Others would interpret an empty string as a single line, even though it's really no lines. Still others would completely "collapse" any blank lines, rather than preserving them as individual empty strings.
For example: https://stackoverflow.com/questions/328 ... ing-gmatch
Here are the test cases:
- An empty text file should return an empty table.
Correct: "" becomes { }
Incorrect: "" becomes { "" }
Incorrect: "" becomes nil
. - A text file consisting of just a newline should return a table with one empty string:
Correct: "\n" becomes { "" }
Incorrect: "\n" becomes { "", "" }
Incorrect: "\n" becomes { }
. - A text file consisting of empty lines should return a table with empty strings in-tact:
Correct: "Hello\nWorld\n\n" becomes { "Hello", "World", "" }
Incorrect: "Hello\nWorld\n\n" becomes { "Hello", "World" }
Incorrect: "Hello\nWorld\n\n" becomes { "Hello", "World", "", "" }
Code: Select all
local function splitlines( str, has_blanks )
res = { }
str = string.gsub( str, "\r\n", "\n" ) -- normalize CRLF (used by Windows OS)
str = string.gsub( str, "\r", "\n" ) -- normalize CR (used by Macintosh OS)
if str ~= "" then
str = string.gsub( str, "\n$", "" ) -- ignore trailing newline
for val in string.gmatch( str .. "\n", "(.-)\n" ) do
if val ~= "" or has_blanks then
table.insert( res, val )
end
end
end
return res
end
- MisterE
- Member
- Posts: 694
- Joined: Sun Feb 16, 2020 21:06
- GitHub: MisterE123
- IRC: MisterE
- In-game: MisterE
Re: Post your code!
what is the license for your code snippets here?sorcerykid wrote: ↑Thu Jun 02, 2022 19:57The following Lua helper function can be used to split the lines of a given input string (such as from a plain text file), into a table. Optionally, blank lines can be suppressed by passing true for the second parameter.
lines = splitlines( str, has_blanks )
I ended up writing this function because the solutions I found online failed to handle blank lines correctly. Some would treat a trailing newline character as a blank line, even though that's just the EOF. Others would interpret an empty string as a single line, even though it's really no lines. Still others would completely "collapse" any blank lines, rather than preserving them as individual empty strings.
For example: https://stackoverflow.com/questions/328 ... ing-gmatch
Here are the test cases:
It's worth noting that this function is also cross-platform aware, so it can accept strings even with a mixture of line-endings: Windows OS, Macintosh OS (legacy), or Linux/BSD are all supported.
- An empty text file should return an empty table.
Correct: "" becomes { }
Incorrect: "" becomes { "" }
Incorrect: "" becomes nil
.- A text file consisting of just a newline should return a table with one empty string:
Correct: "\n" becomes { "" }
Incorrect: "\n" becomes { "", "" }
Incorrect: "\n" becomes { }
.- A text file consisting of empty lines should return a table with empty strings in-tact:
Correct: "Hello\nWorld\n\n" becomes { "Hello", "World", "" }
Incorrect: "Hello\nWorld\n\n" becomes { "Hello", "World" }
Incorrect: "Hello\nWorld\n\n" becomes { "Hello", "World", "", "" }
Code: Select all
local function splitlines( str, has_blanks ) res = { } str = string.gsub( str, "\r\n", "\n" ) -- normalize CRLF (used by Windows OS) str = string.gsub( str, "\r", "\n" ) -- normalize CR (used by Macintosh OS) if str ~= "" then str = string.gsub( str, "\n$", "" ) -- ignore trailing newline for val in string.gmatch( str .. "\n", "(.-)\n" ) do if val ~= "" or has_blanks then table.insert( res, val ) end end end return res end
- LMD
- Member
- Posts: 1400
- Joined: Sat Apr 08, 2017 08:16
- GitHub: appgurueu
- IRC: appguru[eu]
- In-game: LMD
- Location: Germany
- Contact:
Re: Post your code!
For files for line in io.readlines(filename) do ... end or file = io.open(filename); while true do line = file:read(); if not line then break end ... end can be used.
- LMD
- Member
- Posts: 1400
- Joined: Sat Apr 08, 2017 08:16
- GitHub: appgurueu
- IRC: appguru[eu]
- In-game: LMD
- Location: Germany
- Contact:
Re: Post your code!
You can get rid of two string copies:sorcerykid wrote: ↑Thu Jun 02, 2022 19:57Code: Select all
local function splitlines( str, has_blanks ) res = { } str = string.gsub( str, "\r\n", "\n" ) -- normalize CRLF (used by Windows OS) str = string.gsub( str, "\r", "\n" ) -- normalize CR (used by Macintosh OS) if str ~= "" then str = string.gsub( str, "\n$", "" ) -- ignore trailing newline for val in string.gmatch( str .. "\n", "(.-)\n" ) do if val ~= "" or has_blanks then table.insert( res, val ) end end end return res end
1. Use str = string.gsub( str, "\r\n?", "\n" ) to normalize the line endings
2. Check whether there is a trailing newline and don't add one in that case: str = str:match"\n$" and str or (str .. "\n")
I'd also suggest making this an iterator by doing return string.gmatch( str .. "\n", "(.-)\n" ) instead of running the for loop; has_blanks could be implemented by changing the pattern: has_blanks and "(.-)\n" or "(..-)\n".
- sorcerykid
- Member
- Posts: 1847
- Joined: Fri Aug 26, 2016 15:36
- GitHub: sorcerykid
- In-game: Nemo
- Location: Illinois, USA
Re: Post your code!
That requires knowing that you are working only with a file. It doesn't help for general purpose parsing of textual data streams.
- sorcerykid
- Member
- Posts: 1847
- Joined: Fri Aug 26, 2016 15:36
- GitHub: sorcerykid
- In-game: Nemo
- Location: Illinois, USA
Re: Post your code!
I just finished work on a lightweight templating class for JS. This makes creating basic dynamic webpages so much easier. No need for intermixing long, obtuse strings of HTML with Javascript. Now any given sections of an HTML document can be designated as a template, and values can be substituted on-the-fly afterward.
Note: This isn't to be confused with Javascript's builtin "template literals". I much prefer the flexibility and convenience of C's sprintf() function, including the most common format specifiers: %s for string, %d for integer, and %f for floating point, with options for padding, field-width, and precision.
For example, here's the weather dialog for the JT2 server. This is all the code it takes to update after an AJAX request:
And here's the HTML with the designated template sections (note that the comment tags are required):
The basic structure of a template section is as follows:
Each template section is defined individually at the end of the document via the MarkupTemplate class:
The MarkupTemplate class provides three methods:
Note: This isn't to be confused with Javascript's builtin "template literals". I much prefer the flexibility and convenience of C's sprintf() function, including the most common format specifiers: %s for string, %d for integer, and %f for floating point, with options for padding, field-width, and precision.
Code: Select all
function MarkupTemplate( id, args )
{
let elem = document.getElementById( id );
let tmpl = elem.innerHTML;
if( tmpl.search( /^<!--\s[^]+\s-->$/ ) == -1 )
return null;
tmpl = tmpl.slice( 5, -4 );
this.remove = function ( )
{
elem.innerHTML = '';
}
this.update = function ( )
{
let regex = /%([-0])?([0-9]+)?(\.[0-9]+)?([sdf%])/g;
let args = arguments;
let idx = 0;
elem.innerHTML = tmpl.replace( regex, function ( exp, p0, p1, p2, p3 )
{
if( exp == '%%' )
return '%';
else if( idx >= args.length )
return 'undefined';
let type = p3;
let prec = p2 != undefined ? parseInt( p2.substr( 1 ) ) : 6;
let size = p1 != undefined ? parseInt( p1 ) : 0;
let fill = p0 == '0' && type != 's' ? '0' : ' ';
let is_sign = type != 's' && args[ idx ] < 0;
let is_left = p0 == '-';
let str;
switch( type ) {
case 's':
str = args[ idx ];
break;
case 'd':
str = parseFloat( args[ idx ] ).toFixed( 0 );
break;
case 'f':
str = parseFloat( args[ idx ] ).toFixed( prec );
break;
}
while( str.length < size ) {
str = is_left ? str + ' ' : fill + str;
if( fill == '0' && !is_left && is_sign )
str = '-0' + str.substr( 2 ); // fun corner case!
}
idx++;
return str;
} )
}
this.listen = function ( type, listener )
{
elem.addEventListener( type, listener );
}
if( args )
this.update( ...args ); // use initial values
else
this.remove( );
return this;
}
Code: Select all
locale_stats1.update( res.locale_stats.amb_desc, w_captions[ res.locale_stats.amb_desc ] );
locale_stats2.update( res.locale_stats.amb_temp, res.locale_stats.amb_cool, res.locale_stats.amb_wind );
Code: Select all
<TABLE CLASS="infocard" WIDTH="400" BORDER="0" CELLPADDING="8" CELLSPACING="0">
<TR><TH CLASS="titlebar" COLSPAN="2">Weather Conditions</TH></TR>
<TR>
<TH WIDTH="50%" ID="locale_stats1"><!-- <P><IMG SRC="assets/weather_%s.png"><BR>%s</P> --></TH>
<TD WIDTH="50%" ID="locale_stats2"><!-- <H1>%d°F</H1><P><B>Wind Chill:</B> %d°F<BR><B>Wind Speed:</B> %0.1f mph</P> --></TD>
</TR>
</TABLE>
Code: Select all
<ELEM ID="tpl"><!-- HTML to format with support for %d, %s, and %f --></ELEM>
- tpl = MarkupTemplate( id, args )
The MarkupTemplate class provides three methods:
- tpl.remove( )
Clear the template section.
tpl.update( ... )
Format the template section with the given arguments.
- Blockhead
- Member
- Posts: 1697
- Joined: Wed Jul 17, 2019 10:14
- GitHub: Montandalar
- IRC: Blockhead256
- In-game: Blockhead Blockhead256
- Location: Land Down Under
- Contact:
Re: Post your code!
Uppercase tags - Woohoo back to the 90s! Width, border, cellpaddding, cellspacing attributes would be replaced with CSS styles nowadays too. No indictment on your JavaScript or the validity of the HTML, just poking fun at how it's "out of style".sorcerykid wrote: ↑Thu Jun 23, 2022 20:27And here's the HTML with the designated template sections (note that the comment tags are required):
Code: Select all
<TABLE CLASS="infocard" WIDTH="400" BORDER="0" CELLPADDING="8" CELLSPACING="0"> <TR><TH CLASS="titlebar" COLSPAN="2">Weather Conditions</TH></TR> <TR> <TH WIDTH="50%" ID="locale_stats1"><!-- <P><IMG SRC="assets/weather_%s.png"><BR>%s</P> --></TH> <TD WIDTH="50%" ID="locale_stats2"><!-- <H1>%d°F</H1><P><B>Wind Chill:</B> %d°F<BR><B>Wind Speed:</B> %0.1f mph</P> --></TD> </TR> </TABLE>
Also I thought temperature survive also supported metric as an option somewhere, I've seen Wuzzy with °C in screenshots. How will you support that?
/˳˳_˳˳]_[˳˳_˳˳]_[˳˳_˳˳\ Advtrains enthusiast | My map: Noah's Railyard | My Content on ContentDB ✝️♂
- iisu
- Member
- Posts: 220
- Joined: Tue Mar 28, 2017 20:13
- GitHub: iisu
- IRC: iisu
- In-game: iisu
- Location: Internet
Re: Post your code!
From my little juice and ice cream mod I made for JT2*, registering O(n^3) items in one crafting recipe:
Results:
1. Clean crafting guide. 2. An assortment of craftable items. Could be useful with stairs or something.
*The algorithm was not used on the server but you can still hop in there and enjoy fresh juice and ice cream!
Code: Select all
local icecream_flavors = {
["default:cactus"] = "cactus",
["default:orange"] = "orange",
["default:apple"] = "apple",
["farming:blueberries"] = "blueberry",
["farming:raspberries"] = "raspberry",
["farming:melon"] = "melon",
["easter_eggs:chocolate_egg"] = "chocolate",
["easter_eggs:chocolate_egg_dark"] = "chocolate"
}
local add_item_to_group = function (item, group)
local v = minetest.registered_items[item]
if v ~= nil then
local groups = v.groups
groups[group] = 1
minetest.override_item(item, {
groups = groups
})
end
end
minetest.register_craftitem(mod_name .. ":ice_cream", {
description = S("Ice Cream"),
inventory_image = mod_name .. "_icecream.png",
stack_max = 1
})
minetest.register_craft({
output = mod_name .. ":ice_cream",
recipe = {
{ "default:snow", "group:chocolate_egg", "default:snow" },
{ "default:snow", "group:icecream_flavor", "default:snow" },
{ "group:icecream_flavor", "vessels:drinking_glass", "group:icecream_flavor" }
}
})
for _, v in pairs(icecream_flavors) do
for _, w in pairs(icecream_flavors) do
for _, x in pairs(icecream_flavors) do
if w ~= v and x ~= v then
minetest.register_craftitem(mod_name .. ":ice_cream_" .. v .. "_" .. w .. "_" .. x, {
description = S(v .. ", " .. w .. " and " .. x .. " Ice Cream"),
inventory_image = mod_name .. "_icecream_" .. v .. "_left_ball.png^" ..
mod_name .. "_icecream_" .. w .. "_top_ball.png^" ..
mod_name .. "_icecream_" .. x .. "_right_ball.png^" ..
mod_name .. "_icecream_cup.png",
groups = { vessel = 1, not_in_creative_inventory = 1 },
stack_max = 1,
on_use = function (itemstack, user, pointed_thing)
--apply status effect here
return minetest.item_eat(3, "vessels:drinking_glass")(itemstack, user, pointed_thing)
end
})
end
end
end
end
local select_flavors = function (itemstack, player, old_craft_grid, craft_inv)
if itemstack:get_name() == mod_name .. ":ice_cream" then
local v = icecream_flavors[old_craft_grid[7]:get_name()]
local w = icecream_flavors[old_craft_grid[5]:get_name()]
local x = icecream_flavors[old_craft_grid[9]:get_name()]
if w == v or x == v or x == w then
--effectively disables crafting
itemstack:clear()
else
itemstack:set_name(mod_name .. ":ice_cream_" .. v .. "_" .. w .. "_" .. x)
end
end
end
minetest.register_craft_predict(select_flavors)
minetest.register_on_craft(select_flavors)
1. Clean crafting guide. 2. An assortment of craftable items. Could be useful with stairs or something.
*The algorithm was not used on the server but you can still hop in there and enjoy fresh juice and ice cream!
Roses are red, violets are blue. Omae wa mou shindeiru.
- jwmhjwmh
- Developer
- Posts: 125
- Joined: Sun Jul 18, 2021 14:10
- GitHub: TurkeyMcMac
Re: Post your code!
Code: Select all
local function interval_step(interval, correction_rate, step)
local dtime_threshold = interval
local dtime_accum = 0
return function(dtime)
dtime_accum = dtime_accum + dtime
if dtime_accum < dtime_threshold then
return
end
step(dtime_accum)
dtime_threshold = math.max(dtime_threshold + (interval - dtime_accum) * correction_rate, 0)
dtime_accum = 0
end
end
Here's a usage example:
Code: Select all
minetest.register_globalstep(interval_step(1, 0.1, function(dtime)
minetest.chat_send_all(dtime)
end))
Code: Select all
1.08
0.99
0.99
0.99
0.99
0.99
0.99
0.99
0.99
I discovered this mechanism by chance, basically, but it seems to work quite well. Pipeworks uses it now.
- Blockhead
- Member
- Posts: 1697
- Joined: Wed Jul 17, 2019 10:14
- GitHub: Montandalar
- IRC: Blockhead256
- In-game: Blockhead Blockhead256
- Location: Land Down Under
- Contact:
Re: Post your code!
Would registering multiple timers this way tend to result in them all trying to run at almost the same time? Wouldn't that cause lag spikes? How does it compare to sorcerykid's timekeeper?
Trialling it on your local copy? Otherwise I can't see it online, at least not among your various pipeworks branches on GitHub/TurkeyMcMac.
/˳˳_˳˳]_[˳˳_˳˳]_[˳˳_˳˳\ Advtrains enthusiast | My map: Noah's Railyard | My Content on ContentDB ✝️♂
- jwmhjwmh
- Developer
- Posts: 125
- Joined: Sun Jul 18, 2021 14:10
- GitHub: TurkeyMcMac
Re: Post your code!
Probably. One would need some functionality to stagger the timers in order to prevent this.
I haven't seen that before. It looks like it might track expiries in the same way as minetest.after, in which case (actual period) >= (desired period).
Here's the commit where the mechanism was introduced: https://github.com/mt-mods/pipeworks/co ... 81a70b8db0. This allows Pipeworks to run at an average interval of precisely 0.19, for example, as discussed in the associated PR.
Re: Post your code!
I had a lot of trouble with that, so if you want a sure-fire (maybe) to force certain mapgen params:
I mention this because I once saw a Twitch streamer who had trouble when trying various games because they fiddled too much with the GUI and were starting game worlds with the "singlenode" mapgen. If your game is tied with (or simply just "best played with") a particular mapgen, do this.
The for loop is obviously not necessary in this case (that's a remain of my many tries), but can be useful in other cases.
Code: Select all
minetest.set_mapgen_params
{
mgname="v7",
flags="dungeons,caves",
}
minetest.register_on_mapgen_init(function(mgp)
for k,v in pairs{
mgv7_spflags="mountains,noridges,nofloatlands,nocaverns",
} do minetest.set_mapgen_setting(k, v, true) end end)
The for loop is obviously not necessary in this case (that's a remain of my many tries), but can be useful in other cases.
- Skamiz Kazzarch
- Member
- Posts: 619
- Joined: Fri Mar 09, 2018 20:34
- GitHub: Skamiz
- In-game: Skamiz
- Location: la lojbaugag.
Re: Post your code!
A ~110 lines long proof of concept gesture detection mod.
What it does?
Adds a wand. Use the dig key to draw patterns in the air. Mod detects pattern (a sequence of "up", "down", etc...) and... prints it to chat, since it's just a PoC. But it should be simple enough to detect when a 'correct' pattern was entered and make something happen.
This is inspired by the game Arx Fatalis, where you would draw a sequence of runes to cast your spells. Though for my use case I was mostly thinking of it as a very simple way to trigger a wide variety of effects from a single item. An alternative input method, which for many patterns takes less then a second.
Why post the code here, instead of making it a proper mod?
Because while I think its a neat mechanic, I can't be bothered to spend several hours turning this into a fully featured API only for it to maybe get two responses if any, and then fall into obscurity. (yes, that's happened before -.-)
What will I need?
A texture for the wand.
A texture for the particles it places in the air to help you see what you are drawing.
Additional notes:
The gestures depend solely on the player yaw and pitch. Walking around while drawing them has no effect.
In this state it detects only 4 directions, but by changing one line you can change it to 8. It should be pretty obvious where.
On the off chance that this actually tickles someones fancy and they want help getting this to work / an explanation for 'whatever' in the code / anything else, feel free to PM me.
What it does?
Adds a wand. Use the dig key to draw patterns in the air. Mod detects pattern (a sequence of "up", "down", etc...) and... prints it to chat, since it's just a PoC. But it should be simple enough to detect when a 'correct' pattern was entered and make something happen.
This is inspired by the game Arx Fatalis, where you would draw a sequence of runes to cast your spells. Though for my use case I was mostly thinking of it as a very simple way to trigger a wide variety of effects from a single item. An alternative input method, which for many patterns takes less then a second.
Why post the code here, instead of making it a proper mod?
Because while I think its a neat mechanic, I can't be bothered to spend several hours turning this into a fully featured API only for it to maybe get two responses if any, and then fall into obscurity. (yes, that's happened before -.-)
What will I need?
A texture for the wand.
A texture for the particles it places in the air to help you see what you are drawing.
Additional notes:
The gestures depend solely on the player yaw and pitch. Walking around while drawing them has no effect.
In this state it detects only 4 directions, but by changing one line you can change it to 8. It should be pretty obvious where.
On the off chance that this actually tickles someones fancy and they want help getting this to work / an explanation for 'whatever' in the code / anything else, feel free to PM me.
Code: Select all
local modname = minetest.get_current_modname()
local modpath = minetest.get_modpath(modname)
local function get_eye_pos(player)
local player_pos = player:get_pos()
local eye_height = player:get_properties().eye_height
local eye_offset = player:get_eye_offset()
-- player_pos.y = player_pos.y + eye_height
return player_pos + eye_offset + vector.new(0, eye_height, 0)
end
local casters = {}
local function start_casting(player)
local name = player:get_player_name()
casters[name] = {
player = player,
last_ver = player:get_look_vertical(),
last_hor = player:get_look_horizontal(),
last_dir = "none",
sequence = {},
}
end
local function finish_casting(player)
local name = player:get_player_name()
minetest.chat_send_all("Sequence was:")
minetest.chat_send_all(table.concat(casters[name].sequence, ", "))
casters[name] = nil
end
minetest.register_craftitem(modname .. ":wand", {
description = "wand",
inventory_image = "foo_wand.png",
on_use = function(itemstack, user, pointed_thing)
start_casting(user)
end,
-- on righclick release stored spell
})
local function angle_to_dir_4(angle)
if angle < 45 then return "up"
elseif angle < 135 then return "left"
elseif angle < 225 then return "down"
elseif angle < 315 then return "right"
else return "up"
end
end
local function angle_to_dir_8(angle)
if angle < 22.5 then return "up"
elseif angle < 67.5 then return "up_left"
elseif angle < 112.5 then return "left"
elseif angle < 157.5 then return "down_left"
elseif angle < 202.5 then return "down"
elseif angle < 247.5 then return "down_right"
elseif angle < 292.5 then return "right"
elseif angle < 337.5 then return "up_right"
else return "up"
end
end
local function get_dir(last_ver, last_hor, new_ver, new_hor)
local d_ver = new_ver - last_ver
local d_hor = new_hor - last_hor
if d_ver == 0 and d_hor == 0 then return end
-- acount for wraparound
if math.abs(d_hor) > math.pi then
d_hor = (-math.sign(d_hor) * 2 * math.pi + new_hor) - last_hor
end
-- angle is 0 at 12 o'clock and increases counter clock wise
local angle = math.atan2(-d_hor, d_ver) + math.pi
angle = math.deg(angle)
return angle_to_dir_4(angle)
end
minetest.register_globalstep(function(dtime)
for name, cast in pairs (casters) do
local player = cast.player
if (not player:get_player_control().dig) or player:get_wielded_item():get_name() ~= modname .. ":wand" then
finish_casting(player)
end
-- particles
local start_pos = get_eye_pos(player)
local look_dir = player:get_look_dir()
minetest.add_particle({
pos = start_pos + look_dir,
expirationtime = 2,
texture = "foo_particle.png",
glow = 0,
})
-- actual mechanics
local new_ver = player:get_look_vertical()
local new_hor = player:get_look_horizontal()
local new_dir = get_dir(cast.last_ver, cast.last_hor, new_ver, new_hor)
cast.last_ver = new_ver
cast.last_hor = new_hor
if new_dir and new_dir ~= cast.last_dir then
table.insert(cast.sequence, new_dir)
cast.last_dir = new_dir
end
end
end)
-
- Member
- Posts: 155
- Joined: Mon Dec 28, 2020 01:19
- GitHub: loosewheel
- In-game: loosewheel
Re: Post your code!
This was just an experiment to try and (easily?) breakup a long process into smaller running parts. This worked for luajit but not lua 5.1 from the console. It worked in game (luajit). I tried to use debug.sethook to inject yields, but fails with yield across c func boundary error. So a function has to be called to yield from the added function.
This is the output of the above test.
EDIT: removing the pcall from the thread_func and leaving the first return value from coroutine.resume seems to work the same but also works with lua 5.1
Code: Select all
-- long_process --------------------------------------------------------
local RUN_INTERVAL = 0.1
local processes = { index = 0, current = 0 }
local after_job
local function run_processes ()
if #processes > 0 then
processes.index = (processes.index % #processes) + 1
processes.current = processes.index
processes[processes.index].resume ()
processes.current = 0
end
if #processes > 0 then
after_job = minetest.after (RUN_INTERVAL, run_processes)
else
processes.index = 0
processes.current = 0
after_job = nil
end
end
local long_process = { }
function long_process.remove (id)
if id then
for i = 1, #processes, 1 do
if processes[i].id == id then
if processes.current == i then
processes.current = 0
elseif processes.current > i then
processes.current = processes.current - 1
end
if processes.index >= i then
processes.index = processes.index - 1
end
table.remove (processes, i)
return true
end
end
elseif processes[processes.current] then
if processes.index >= processes.current then
processes.index = processes.index - 1
end
table.remove (processes, processes.current)
processes.current = 0
return true
end
return false
end
function long_process.breaker (id)
if id then
for i = 1, #processes, 1 do
if processes[i].id == id then
return processes[i].breaker
end
end
elseif processes[processes.current] then
return processes[processes.current].breaker
end
return nil
end
function long_process.add (func, callback, ... )
local args = { ... }
local id = math.random (1000000)
local thread
local function thread_breaker ()
coroutine.yield (thread)
end
local function thread_func ()
local results = { pcall (func, unpack (args)) }
return unpack (results)
end
local function thread_resume ()
local results = { coroutine.resume (thread) }
if coroutine.status (thread) == "dead" then
table.remove (results, 1)
if callback then
callback (unpack (results))
end
long_process.remove (id)
end
end
thread = coroutine.create (thread_func)
if thread and coroutine.status (thread) == "suspended" then
processes[#processes + 1] =
{
id = id,
resume = thread_resume,
breaker = thread_breaker,
callback = callback
}
if not after_job then
after_job = minetest.after (RUN_INTERVAL, run_processes)
end
return id
end
return nil
end
-- implementation --------------------------------------------------------
local long_id
local function test (count)
local breaker = long_process.breaker () or function () end
for i = 1, count, 1 do
for j = 1, 100000, 1 do
local s = "test"
end
if (i % 1000) == 0 then
print ((i / 1000).." break")
breaker ()
end
--if i == 3000 then
--print ("remove")
--long_process.remove ()
--end
end
return "test returned"
end
local function test_callback (result, ...)
local args = { ... }
print ("test_callback", result, unpack (args))
end
print (test (10000))
long_id = long_process.add (test, test_callback, 10000)
--minetest.after (0.5, function () long_process.remove (long_id) end)
Code: Select all
1 break
2 break
3 break
4 break
5 break
6 break
7 break
8 break
9 break
10 break
test returned
1 break
2 break
3 break
4 break
5 break
6 break
7 break
8 break
9 break
10 break
test_callback true test returned
Code: Select all
local function thread_func ()
return func (unpack (args))
end
--local function thread_func ()
--local results = { pcall (func, unpack (args)) }
--return unpack (results)
--end
local function thread_resume ()
local results = { coroutine.resume (thread) }
if coroutine.status (thread) == "dead" then
--table.remove (results, 1)
if callback then
callback (unpack (results))
end
long_process.remove (id)
end
end
Who is online
Users browsing this forum: No registered users and 17 guests