work in progress libraries are "client" and "server" stuff in both is also put in "common" note, behaviour might be subtly different all files must use "/" as a path separator (Unix Master Race) valid characters are listed here (ignoring the path separator): -.0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ[]_abcdefghijklmnopqrstuvwxyz anything else is invalid and will just be outright rejected furthermore, no paths will be accepted where a . follows a / this means that hidden files on unixlikes will not be accepted! files that are meant to be intercepted should be prefixed with a * e.g. "*thismap" maximum path length is 128 stuff that's actually implemented will be marked with an @ lua base library stuff: math.* @ string.* @ pcall @ error @ print @ loadstring @ loadfile @ dofile @ _G @ nothing else yet (TODO: work out what we want to keep, as there's stuff being used that isn't listed here!) check http://www.lua.org/manual/5.1/ for more info on these some notes: - dofile(x) = loadfile(x)() - loadfile(x) = common.fetch_block("lua", x) - common.fetch_block(ftype, x) = local obj = common.fetch_start(ftype, x) if obj ~= true then return obj end while true do local obj = common.fetch_poll() if obj ~= false then return obj end yield() end - just about every "load" function is based on common.fetch_block common.base_dir @ current base directory (minus the "pkg/" bit) writing to this string will *not* change the base dir, so *don't write to it* or else you'll just screw up your own code! common.version = {cmp={w,x,y,a,z},num,str="w.x.ya-z"} @ current engine version cmp is for the individual version components num is a comparable number (bit count for each: 5,5,7,5,10) str is the version as a string client/server.hook_tick = fn(sec_curtime, sec_delta)->sec_wait @ sets a hook called every "tick" returns a number telling it how long it wants to wait for "sec_curtime" is the current time elapsed from the start "sec_delta" is the time elapsed from the start of the last call to the hooked function setting this to nil will quit your game NOTE: DO NOT USE COMMON FOR THIS HOOK! (only client/server is accepted) server.hook_file = fn(sockfd, ftype, fname)->object sets a hook to intercept networked file requests "sockfd" is the socket pertaining to the file request can return nil to cancel the request can return true to perform the default request otherwise, return an object to be serialised currently supported values for ftype: - "icemap" the rest must be either nil or true. client.hook_render = fn() @ sets a hook called every frame this is the only place where it is safe to render stuff such as models and HUDs and whatnot client.hook_key = fn(key, state, modif) @ sets a hook called every time a key changes state is either true or false client.hook_mouse_button = fn(button, state) @ sets a hook called every time a mouse button changes state is either true or false client.hook_mouse_motion = fn(x, y, dx, dy) @ sets a hook called every time the mouse is moved x, y are the current position dx, dy are the delta positions you will probably use the latter pair if the mouse is "locked" as x, y are not defined the same on all platforms! server.hook_connect = fn(sockfd, addrinfo) @ sets a hook called every time a client connects note, sockfd == true for "local multiplayer" mode "addrinfo" is a table which may or may not contain: - "proto" / "addr": protocol / address used, one of the following: - "local" / nil - "tcp/ip" or "tcp/ip6" / { ["ip"] = address, ["host"] = hostname or nil, ["cport"] = client port, ["sport"] = server port, } note, not all of the features are supported. server.hook_disconnect = fn(sockfd, server_force, reason) @ sets a hook called every time a client disconnects "server_force" is true where the server forces the disconnect client.mouse_lock_set(state) @ locks / unlocks the mouse depending on "state" client.mouse_visible_set(state) @ shows / hides the mouse depending on "state" client.mouse_warp(x, y) @ moves the mouse to x,y obj = common.fetch_start(ftype, fname) initiates a file fetch "ftype" is one of the following: - "lua": lua script - "map": map (autodetect) - "icemap": map (icemap) - in-memory maps are serialised as THIS. - "vxl": map (vxl) - CANNOT SAVE IN THIS FORMAT. - "pmf": pmf model - "tga": tga image - "json": json data - "log": log data (TODO) - "wav": wav sound (TODO) for the server, this just loads the file from the disk. for the client, all clsave/* stuff is taken from the disk, but all other files are downloaded from the server. returns true if the fetch has started, nil if there is an error, or the object if this was an immediate load. if there is already a file in the queue, this will return "nil". obj, csize, usize, amount = common.fetch_poll() polls the "obj" is one of the following: - "nil" if transfer aborted or nothing is being fetched - in this case, all other fields will be nil - "false" if still downloading - the object you requested - in this case, another poll will just return nils "amount" is in the range 0 <= "amount" <= 1, and indicates how much is downloaded "csize" is the compressed size of the file "usize" is the uncompressed size the two sizes will be nil while unknown. note, all vxl maps will be converted to icemap before sending. obj = common.fetch_block(ftype, fname) @ fetches a file using common.fetch_* simply returns "nil" on error if there is already something being fetched, it will return "nil", too map = common.map_load(fname, fmt = "auto") @ loads a map, either "vxl" or "icemap" use "auto" to autodetect this will return nil if it fails. map = common.map_new(lx, ly, lz) @ creates a new map note, ly is the *height* of the new map also all dimensions must be powers of two, otherwise this will fail horribly! this will throw a lua error if it fails. common.map_free(map) @ free the given map if you don't do this then it's memoryleaktopia (plus i'm allowed to kill you) ALSO DON'T DO THIS TO THE CURRENT "SET" MAP TODO: clean up all models on game kill common.map_set(map) @ sets the "current" map to "map" note, "map" can be nil to disable rendering anything map = common.map_get() @ sets the "current" map may return "nil" common.map_save(map, fname, fmt = "icemap") @ saves a map to a file, either "vxl" or "icemap" this will throw a lua error if it fails. r, g, b, dist = client.map_fog_get() @ gets the current fog colour / distance client.map_fog_set(r, g, b, dist) @ sets the current fog colour / distance lx, ly, lz = common.map_get_dims() @ gets the map's dimensions these will be nil if there is no map loaded table = common.map_pillar_get(px, pz) @ returns a full pillar of data, skipping the total size header this will be nil if there is no map loaded note, the data wraps around here (wrt px,pz)! common.map_pillar_set(px, pz, table) @ sets a full pillar of data this will be nil if there is no map loaded the data is checked before setting, and will throw a lua error if it fails. note, the data wraps around here (wrt px,pz)! WARNING: You MUST update the side pillars in your code! You MUST ALSO fill in the newly-exposed blocks, as well as remove the newly-unexposed blocks! Otherwise, there WILL be gaps! WE'RE NOT DOING IT FOR YOU! client.camera_point(dx, dy, dz, zoom = 1.0, roll = 0.0) @ points the camera in a direction with zoom factor "zoom" and roll "roll" (in radians, sorry) client.camera_point_sky(dx, dy, dz, zoom = 1.0, sx = 0.0, sy = -1.0, sz = 0.0) @ points the camera in a direction with zoom factor "zoom" and sky arrow sx,sy,sz client.camera_move_local(dx, dy, dz) @ moves the camera in the camera-local direction (dx,dy,dz) client.camera_move_global(dx, dy, dz) @ moves the camera in the world direction (dx,dy,dz) client.camera_move_to(px, py, pz) @ moves the camera to the world position (px,py,pz) px, py, pz = client.camera_get_pos() @ gets the camera's position dx, dy, dz = client.camera_get_forward() @ gets the camera's forward vector client.camera_shade_set(xn, yn, zn, xp, yp, zp) sets the face shading levels per face (0 <= v <= 1) sides relative to map are: east, bottom, south, west, top, north pmf = common.model_new(bonemax = 5) @ creates a new model remember to free it when you're done as this is only a light userdata "bonemax" is the initial maximum number of "bones" this model can have initially useful if you know exactly how many you'll need currently the number of bones is limited to 256 pmf = common.model_load_pmf(fname) @ loads a pmf from a file remember to free it when you're done as this is only a light userdata limits of bones / points still applies here; files which exceed these WILL be rejected! returns nil on failure. success = common.model_save_pmf(pmf, fname) @ saves a pmf to a file common.model_free(pmf) @ free the given model if you don't do this then it's memoryleaktopia (plus i'm allowed to kill you) TODO: clean up all models on game kill len = common.model_len(pmf) @ get the number of bones in this model pmf, boneidx = common.model_bone_new(pmf, ptmax = 20) @ creates a new bone "ptmax" is the initial maximum number of "points" this bone can have initially useful if you know exactly how many you'll need currently the number of points is limited to 4096 WARNING: YOU *MUST* TAKE THE pmf VALUE RETURNED AND *NOT* USE THE OLD ONE ANY LONGER! this is because realloc() is called on this dynamic list and it can seriously crash badly JUST SAYING common.model_bone_free(pmf, boneidx) @ removes a bone from the model name, table = common.model_bone_get(pmf, boneidx) @ gets a table with every point in the given bone each entry in the table has the following keys: uint16_t radius; // fixed point 8.8 int16_t x,y,z; // fixed point 8.8 uint8_t r,g,b; the reserved field of each point is inaccessible from this API common.model_bone_set(pmf, boneidx, name, table) @ replaces the bone's contents with that in the table note, bones will be rejected if: - name is > 15 chars long - radius,x,y,z,r,g,b are missing - 0 <= radius < 65536 fails - -32768 <= x,y,z < 32768 fails - 0 <= r,g,b < 256 fails these exceptions will throw a lua error boneidx = common.model_bone_find(pmf, name) @ finds the first bone with the given name note, this is case sensitive if it cannot be found, this returns nil client.model_render_bone_global(pmf, boneidx, px, py, pz, ry, rx, ry2, scale) @ renders a bone at world position (px,py,pz), rotated around Y by "ry" radians, then around X by "rx" radians, then around Y by "ry2" radians, and scaled "scale" times client.model_render_bone_local(pmf, boneidx, px, py, pz, ry, rx, ry2, scale) @ renders a bone at camera-local position (px,py,pz), rotated around Y by "ry" radians, then around X by "rx" radians, then around Y by "ry2" radians, and scaled "scale" times width, height = client.screen_get_dims() @ gets the dimensions of the screen img, width, height = common.img_load(fname) @ loads an image with filename "fname" remember to free it when you're done as this is only a light userdata if this fails, img, width, height will all be nil img = common.img_new(width, height) @ creates a new 32bpp RGBA image remember to free it when you're done as this is only a light userdata if this fails, img will be nil common.img_pixel_set(img, x, y, color) @ sets the pixel x,y on image "img" to "color" drawing is clipped common.img_free(img) @ free the given image if you don't do this then it's memoryleaktopia (plus i'm allowed to kill you) width, height = common.img_get_dims(img) @ gets the image's dimensions client.img_blit(img, dx, dy, width = iwidth, height = iheight, sx = 0, sy = 0, color = 0xFFFFFFFF) @ blits an image onto screen position dx, dy "color" indicates a base 0xAARRGGBB colour to use this is clipped to fit! client.img_blit_to(target, img, dx, dy, width = iwidth, height = iheight, sx = 0, sy = 0, color = 0xFFFFFFFF) @ blits an image onto another image at position dx, dy "color" indicates a base 0xAARRGGBB colour to use this is clipped to fit! client.img_fill(img, color) @ fills an image with a solid colour tab = common.json_parse(str) @ parses the JSON string "str" yes, this parser is anal-retentive, and only allows one specific case where it breaks the rules! it also errors if it encounters a char 0 in the middle of a string. returns nil on error tab = common.json_load(fname) @ loads a JSON file and parses it returns nil on error str = common.json_pack(tab) takes a table and spits out valid JSON (aside from encoding details) the following types are supported: - numbers - strings - true, false, nil - tables with only string keys - tables with only numeric keys from 1 through # and therefore the following types are NOT supported: - functions/closures of ANY sort (C or Lua) - tables with mixed hash/numeric keys - userdata (light or otherwise) and i think that's about it for types really returns nil on error success = common.json_save(fname, tab) takes a table and saves it to a file as JSON returns true on success, false on error str = common.net_pack(fmt, ...) @ packs data into a string format is as such: b/B = signed/unsigned 8-bit [def 0] h/H = signed/unsigned 16-bit [def 0] i/I = signed/unsigned 32-bit [def 0] f = single-precision 32-bit float [def 0.0] d = double-precision 64-bit float [def 0.0] z = zero-terminated string [def ""] #s = fixed-length string (replace # with a decimal number) [def ""] throws a lua error if the fmt syntax is invalid. spews defaults if not enough arguments are provided. ..., remain = common.net_unpack(fmt, str) @ unpacks data from a string will attempt to decode from start to end "remain" is the remainder of the string which was not decoded returns nil for fields that could not be decoded success = common.net_send(sockfd, str) @ sends a packet "sockfd" is ignored C->S and should be nil S->C local multiplayer should set "sockfd" to "true" str, sockfd = common.net_recv() @ receives a packet for C->S "sockfd" is nil S->C local multiplayer will result in "sockfd" being "true" returns nil if nothing is there server.net_kick(sockfd, reason) kicks a client from the server fails silently if sockfd is invalid, or sockfd is closed / errors out throws a lua error for other weird errors. wav = common.wav_load(fname) loads a sound with filename "fname" remember to free it when you're done as this is only a light userdata common.wav_free(wav) free the given sound if you don't do this then it's memoryleaktopia (plus i'm allowed to kill you) client.wav_cube_size(size) sets the size of a block in metres for sound calculations chn = client.wav_play_global(wav, x, y, z, vol = 1.0, freq_mod = 1.0, vol_spread = ?) play the given sound at the given world position TODO: define vol_spread properly returns an index of a channel returns nil on error chn = client.wav_play_local(wav, x = 0.0, y = 0.0, z = 0.0, vol = 1.0, freq_mod = 1.0, vol_spread = ?) play the given sound at the given camera-local position returns an index of a channel returns nil on error exists = client.wav_chn_exists(chn) checks if an allocated channel still exists if a channel stops, it is garbage collected success = client.wav_chn_update(chn, x = nil, y = nil, z = nil, vol = nil, freq_mod = nil, vol_spread = nil) updates information pertaining to a channel any field which is nil is not affected returns false if the channel no longer exists client.wav_kill(chn) stops and removes a channel if chn == "true", kills all channels font_mini Small ASCII font, for most body text. font_large Big ASCII font, for headers and stuff. font_digits Big numeric font, for HUD counters. gui_index_mini(i) Character lookup helper for the mini font gui_index_digit(i) Character lookup helper for the digit font gui_create_fixwidth_font(image, char_width, char_height, indexing_fn, shadow) Creates a new fixed width bitmap font. compute_unwrapped(x, y, color, string) Computes specific glyphs and positions needed from a string, color, and x/y coordinates. compute_ctab(ctab, x, y) Computes specific glyphs and positions from a color table, where each line of the table contains a string "msg" and a color "color", and will be positioned underneath the previous line. compute_wordwrap(width, x, y, color, string) Computes specific glyphs and positions from a word-wrapped string, within the width argument. dimensions(data) Get the AABB dimensions of text(l,r,t,b, width, height) given precomputed text data calc_shadow(color) Calculate the shadow strength from the percieved luminance of the font; brighter color = darker shadow print(x, y, color, string, buffer=nil) Print text with topleft at x, y, color c, string str. buffer defines where the text is drawn - to the screen or to an offscreen buffer. print_precomputed(data, offx, offy, buffer=nil) Print precomputed text to the given buffer. print_wrap(wp, x, y, c, str, buffer) Print text with minimum-space wordwrapping, pixelwidth wp, topleft at x, y, color c, string str alarm{time=1, progress=0, active=true, loop=false, preserve_accumulator=true, on_frame=nil, on_trigger=nil} Create an alarm with an abstract time value. time: The target amount of time before the alarm triggers. progress: How much time has elapsed(counting from 0 to the target time). active: Whether the alarm is presently ticking. loop: Whether the alarm loops. preserve_accumulator: If the alarm loops, allow the leftover time to carry into the next loop. on_frame: A callback run every frame. Recieves the time delta. on_trigger: A callback run each time the alarm reaches its target time. Recieves the time delta. gui_create_scene(width, height, shared_rate=1/60) Creates a new GUI scene of designated width and height, and a shared update rate in seconds equal to shared_rate. The GUI system is modelled similarly to the Flash AS3 APIs. A GUI scene can contain any number of display objects, ordered in a tree structure. Each time the GUI is ticked, it processes an event pipeline. An event is a message containing a type and some attached data. When an event reaches a display object with a listener of the same type, the listener's callback is called with the event's data. These events are available: GE_DELTA_TIME The delta time passed in when listeners are pumped. You can use this to drive tweening over time, for example. Callback passes in the dT value. GE_SHARED_ALARM A tick running at a fixed rate. This is an alternative to delta time for situations where you specifically need a fixed rate, for example, an animation that relies on multiplying previous values every step. Callback passes in the dT value of the shared alarm timer. GE_KEY A keypress(up or down) event. Callback passes in {key(int), state(bool), modif(int bitmask)} GE_BUTTON A mapped button press(up or down) event. Use this when you expect the button to be reassignable. Callback passes in {key(int), button{name(string), desc(string)}, state(bool), modif(int bitmask)} GE_MOUSE A mouse movement event. Callback passes in {x(number), y(number), dx(number), dy(number)} GE_MOUSE_BUTTON A mouse button press(up or down). Callback passes in {button(int), down(bool)} #