Fork me on GitHub

hump Helper Utilities for More Productivity

Introduction^ top

Helper Utilities for a Multitude of Problems is a set of lightweight helpers for the excellent LÖVE Engine.

It currently features

hump differs from other libraries in that every component is independent of the remaining ones (apart from camera.lua, which depends on the vector type, and sequence.lua which requires ringbuffer.lua). hump's footprint is very small and thus should fit nicely into your projects.

Documentation^ top

Below is the documentation of the various modules. You can directly jump to a module by clicking these:

gamestate.lua^ top

view source

Useful to separate different states of your game (hence "gamestate") like title screens, level loading, main game, etc. Each gamestate can have it's own update(), draw(), keyreleased(), keypressed() and mousereleased() which correspond to the ones defined in love. See the callback list for more details.

Additionally, each gamestate can define a enter(pre, ...) and leave() function, which are called when using Gamestate.switch(to, ...). Do not attempt to use these functions yourself to initialize the first gamestate, use the switch function instead.

The module defines the following functions:

Gamestate.new()New gamestate
Gamestate.switch(to, ...)Switch gamestate
Gamestate.update(dt)Call update callback
Gamestate.draw()Call draw callback
Gamestate.keypressed(key, unicode)Call keypressed callback
Gamestate.keyreleased(key)Call keyreleased callback
Gamestate.mousereleased(x,y,btn)Call mousereleased callback
Gamestate.registerEvents()Register all callbacks

function Gamestate.new()^ top Create a new gamestate.
Returns:The new (but empty) gamestate object.
function Gamestate.switch(to, ...)^ top Switch the gamestate.

Calls leave() on the currently active gamestate.

Calls enter(current, ...) on the target gamestate, where current is the gamestate before the switch and ... are the additionals arguments given to Gamestate.switch.

Parameters:[gamestate]to:target gamestate.
...:additional arguments to pass
Returns:the result of to:enter(current, ...)
function Gamestate.update(dt)^ top Calls update(dt) on current gamestate.
function Gamestate.draw()^ top Calls draw() on current gamestate.
function Gamestate.keypressed(key, unicode)^ top Calls keypressed(key, unicode) on current gamestate.
function Gamestate.keyreleased(key)^ top Calls keyreleased(key) on current gamestate.
function Gamestate.mousereleased(x,y,btn)^ top Calls mousereleased(x,y,btn) on the current gamestate.
function Gamestate.registerEvents()^ top Registers all above events so you don't need to call then in your love.* routines.

It is an error to call this anywhere else than love.load(), since it overwrites the callbacks. Don't worry though, your callbacks will still be executed.

List of callbacks^ top

enter(previous, ...) Gets called upon entering the state. Don't call this yourself, use Gamestate.switch instead.
leave() Gets called upon leaving the state. The same warning as with enter applies.
update(dt) Manually called by Gamestate.update, or automatically like love.update when using Gamestate.registerEvents().
draw() Manually called by Gamestate.draw, or automatically like love.draw
keypressed(key, unicode) Manually called by Gamestate.keypressed, or automatically like love.keypressed
keyreleased(key) Manually called by Gamestate.keyreleased, or automatically like love.keyreleased
mousereleased(x,y,btn) Manually called by Gamestate.mousereleased, or automatically like love.mousereleased

timer.lua^ top

view source

This module consists of two parts:

All functions are higher order functions, which is a concept that may seem confusing at first. The example shows how to use this concept.

Functions:

Timer.add(delay, func)Add a delayed function
Timer.addPeriodic(delay, func, count)Add a periodic function
Timer.clear()Clear functions
Timer.update(dt)Update timer
Interpolator(length, func)Create interpolating function

Note the . (dot) in the function names. It is an error to call Timer.add with a colon! If you get weird errors, that might be the cause.

The Timer stores it's timed functions in the table Timer.functions. You can manipulate it, but probably shouldn't.

function Timer.add(delay, func)^ top Add a timed function. The function will be executed when delay seconds have elapsed.

Note that there is no guarantee that the delay will be exceeded. It is, however, guaranteed that the function will not be executed before the delay has passed.

If the function is called, it will receive itself as only parameter. This may be useful to implement the periodic behavior of Timer.addPeriodic (see the example).

Parameters:[number]delay: Time that has to pass before the function is called
[function]func:The function to be called
function Timer.addPeriodic(delay, func, count)^ top Add a periodic timed function, i.e. a function that will be called every delay seconds.

The same things as with Timer.add apply.

Parameters:[number]delay: Time that has to pass before the function is called
[function]func:The function to be called.
[optional number]count: Number of times the function should be called. If omitted, the function loops indefinitely.
function Timer.clear()^ top Clears all timers. That is, it resets the table Timer.functions.
function Timer.update(dt)^ top Update timers and execute functions if the deadline is reached. Use this function in love.update(dt).
Parameters:[number]dt: Time that has passed since the last Timer.update.
function Interpolator(length, func)^ top Helper to create interpolating functions, i.e. functions that changes it's behavior depending on how much time has passed.

The interpolating function func will receive the fraction (in respect to the supplied length) of time that has passed, but 1.0 at max.

Interpolator creates a function that will have to be called in love.update, which will itself execute func with the right parameters. It returns true as long as the interpolation is not yet finished or nil if the interpolation stopped as well as any parameters that the function returns.

Parameters:[number]length:Interpolation length.
[function]func:Interpolating function.
ReturnsA function inter(dt) with argument dt that has to be called in love.update.

Example usage

Timer example ^ top This example will print the number of elapsed seconds in the top left corner using a periodic function and show five circles one after another using delayed functions:
time = 0
circles = {}
function love.load()
    Timer.add(1, function() circles[#circles+1] = {x = 100, y = 100, r = 10} end)
    Timer.add(2, function() circles[#circles+1] = {x = 130, y = 130, r = 20} end)
    Timer.add(3, function() circles[#circles+1] = {x = 180, y = 180, r = 30} end)
    Timer.add(4, function() circles[#circles+1] = {x = 250, y = 250, r = 40} end)
    Timer.add(5, function() circles[#circles+1] = {x = 340, y = 340, r = 50} end)
    Timer.addPeriodic(1, function() time = time + 1 end)

    love.graphics.setBackgroundColor(255,255,255)
    love.graphics.setColor(0,0,0)
end

function love.update(dt)
    Timer.update(dt)
end

function love.draw()
    for _,c in ipairs(circle) do
        love.graphics.circle('fill', c.x, c.y, c.r)
    end
end
Implementing Timer.addPeriodic() ^ top Since the function will receive itself as parameter when called, it is easy to implement a function that will reschedule itself:
Timer.add(1, function(func) print("foo") Timer.add(1, func) end)
Using Interpolator ^ top This example uses an interpolating function to fade the background from black to white and move a circle along the x-axis:
function love.load()
    love.graphics.setBackgroundColor(0,0,0)
    love.graphics.setColor(0,0,0)
end

xpos = 100
fader = Interpolator(5, function(frac) love.graphics.setBackgroundColor(frac*255,frac*255,frac*255) end)
mover = Interpolator(10, function(frac) xpos = 10 + 600 * frac end)
function love.update(dt)
    fader(dt)
    mover(dt)
end

function love.draw()
    love.graphics.circle(xpos, 300, 80)
end

vector.lua^ top

view source

A vector class implementing nearly everything you want to do with vectors, and some more.

Accessors:

[number] vector.xx-coordinate of the vector
[number] vector.yy-coordinate of the vector

Function overview (click item for more details):

vector(x,y)constructor
isvector(v)type check
vector:clone()deep copy
vector:unpack()element unfolding
OperatorsArithmetics and ordering
vector:permul(other)per-element multiplication
vector:len()vector length
vector:len2()squared vector length
vector:dist(other)distance to vector
vector:normalized()normalize vector
vector:normalize_inplace()normalize vector in-place
vector:rotated(phi)rotate vector
vector:rotate_inplace(phi)rotate vector in-place
vector:perpendicular()quick rotation by 90°
Vecor:projectOn(v)projection on other vector
Vecor:cross(v)cross product of two vectors

function vector(x,y)^ top Creates a new vector. Element access with v.x and v.y.
Parameters:[number]x:x coordinate
[number]y:y coordinate
Returns:the vector
function isvector(v)^ top Tests for vector type.
Parameters:v:variable to test
Returns:true if v is a vector
function vector:clone()^ top Clones a vector. Use when you do not want to create references.
Returns:New vector with the same coordinates.
function vector:unpack()^ top Unpacks the vector.
Returns:the coordinate tuple x, y
Example:
v = vector(1,2)
print(v:unpack()) -- prints "1     2"
Operators^ top

Arithmetic (+, -, *, /) and comparative operators (==, <=, <) are defined:

  • + and - only work on vectors. - is also the unary minus (e.g. print(-vector(1,2)) -- prints (-1,-2))
  • a * b works on vectors and numbers:
    • If a is a number and b is a vector (or vice versa), the result the scalar multiplication.
    • If a and b both are vectors, then the result is the dot product.
  • a / b is only defined for a being a vector and b being a number. Same as a * 1/b.

<= and < sort lexically, i.e. a < b is true if it holds: a.x < b.x or a.y < b.y if a.x == b.x

function vector:permul(other)^ top Perform element-wise multiplication, e.g. (2,3) x (3,4) = (6,12)
function vector:len()^ top Get length of vector.
function vector:len2()^ top Get squared length.
function vector:dist(other)^ top Get distance to other vector.
Example:
a,b = vector(0,1), vector(1,0)
print(a:dist(b)) -- prints 1.4142135623731
function vector:normalized()^ top Get normalized vector. The original vector remains unchanged.
function vector:normalize_inplace()^ top Normalize vector and return it.

Warning: This will change the state of all references to this vector.

function vector:rotated(phi)^ top Get rotated vector. The original vector remains unchanged.
Parameters:[number]phi:Rotation angle in radians.
Sketch:Rotated vector
function vector:rotate_inplace(phi)^ top Rotate the vector and return it.

Warning: This will change the state of all references to this vector.

function vector:perpendicular()^ top Get vector rotated by 90° clockwise, e.g. the vector (0,1) results in (1,0).
Sketch:Perpendicular vector
function vector:projectOn(v)^ top Project this vector onto another one, e.g. (2,2) projected onto (1,0) results in (2,0).
Parameters:[vector]vVector to project onto
Returns:Vector with direction of v and length according to projection.
Sketch:Projected vector
function vector:cross(v)^ top Calculates cross product between two vectors, i.e.:

Treats both vectors as vectors of the xy-plane (z-coordinate =0) and calculates a vector perpendicular on them. Because the result is perpendicular on to the input vectors, it has the form (0,0,z). The z-coordinate is returned.

Parameters:[vector]vVector to calculate the cross product with
Returns:Cross product as described above

class.lua^ top

view source

class.lua provides a simple and easy to use class system. Albeit it's limitations, class.lua offers multiple inheritance and minimal reflection capabilities. Note that only methods will be inherited and runtime changes of a superclass won't affect subclasses.

The module only consists of three functions:

Class(constructor)define class
Class{name = name, constructor}define named class
Interface(name)define interface
Inherit(class, super, ...)subclassing

For an example, see below. Also be sure to read the warning regarding metamethods.

function Class(constructor)^ top Creates a new unnamed class.
Parameters:[optional function]constructor A function used to construct the class. The first parameter of this function is the object, the others are parameters given upon construction.
Returns:the new class
Example
Feline = Class(function(self, size, weight)
self.size = size self.weight = weight
end)

function Feline:stats()
return string.format("size: %.02f, weight %.02f", self.size, self.weight)
end

garfield = Feline(.7, 45)
felix = Feline(.8, 12)

print("Garfield: " .. garfield:stats(), "Felix: " .. felix:stats())
function Class{name = name, constructor}^ top Create a named class, i.e. define a __tostring metamethod. Parameters are the same as above. Great for debugging. Both name and constructor can be omitted (but why would you want to?)
Example:
Feline = Class{name = "Feline", function(self, size, weight)
self.size = size self.weight = weight
end}

print(Feline) -- prints 'Feline'
function Interface(name)^ top Shortcut to Class{name = name}, i.e. a possibly named class without constructor.
function Inherit(class, super, ...)^ top Add functions of super to class. Multiple interfaces can be defined.

super's constructor can be accessed via super.construct(self). See example below.

Example usage

Everybody loves cats!^ top
Feline = Class{name = "Feline", function(self, size, weight)
self.size = size self.weight = weight
end}

function Feline:stats()
return string.format("size: %.02f, weight %.02f", self.size, self.weight)
end

function Feline:speak() print("meow") end


Cat = Class{name = "Cat", function(self, name, size, weight)
Feline.construct(self, size, weight)
self.name = name
end}
Inherit(Cat, Feline)

function Cat:stats()
return string.format("name: %s, %s", self.name, Feline.stats(self))
end


Tiger = Class{name = "tiger", function(self, size, weight)
Feline.construct(self, size, weight)
end}
Inherit(Tiger, Feline)

function Tiger:speak() print("ROAR!") end

felix = Cat("Felix", .8, 12)
hobbes = Tiger(2.2, 68)

print(felix:stats(), hobbes:stats())
felix:speak()
hobbes:speak()

Warning

Nothing is perfect^ top

Be careful when using metamethods like __add or __mul: When subclass inherits those methods from a superclass, but does not overwrite them, the result of the operation may be of the type superclass. Consider the following:

A = Class(function(self, x) self.x = x end)
function A:__add(other) return A(self.x + other.x) end
function A:print() print("A:", self.x) end

B = Class(function(self, x, y) A.construct(self, x) self.y = y end)
Inherit(B, A)
function B:print() print("B:", self.x, self.y) end
function B:foo() print("foo") end

one, two = B(1,2), B(3,4)
result = one + two
result:print()  -- prints "A:    4"
result:foo()    -- error: method does not exist

camera.lua^ top

view source Depends on vector.lua

Camera object to display only a partial region of the game world. The region can be zoomed and rotated. You can transform camera coordinates to world coordinated (e.g. get the location of the mouse in the game world). It is possible to have more than one camera per game.

Accessors:

[vector] camera.posThe camera's position. This is also the position it looks at.
[number] camera.zoomThe camera's zoom level.
[number] camera.rotThe camera's rotation.

The module defined the following funtions:

Camera(pos, zoom, rotation)create new camera
camera:rotate(phi)rotate camera
camera:translate(t)move camera
camera:draw(func)apply camera transformations on function
camera:predraw()apply camera transformations
camera:postdraw()revert camera transformations
camera:toCameraCoords(p)get camera coordinates of a point
camera:toWorldCoords(p)get world coordinates of a point
camera:mousepos()get world coordinates of mouse

function Camera(pos, zoom, rotation)^ top Create a new camera with position pos, zoom zoom and rotation rotation.
Parameters:[optional vector]pos: Initial position of the camera. Defaults to (0,0).
[optional number]zoom: Initial zoom. Defaults to 1.
[optional number]rotation: Initial rotation in radians. Defaults to 0.
Returns:a new camera object
function camera:rotate(phi)^ top Rotate camera by phi radians. Same as camera.rot = camera.rot + phi.
function camera:translate(t)^ top Translate (move) camera by vector t. Same as camera.pos = camera.pos + t.
Parameters:[vector]t: Translation vector
function camera:draw(func)^ top Apply camera transformation to drawings in function func. Shortcut to camera:apply() and camera:deapply() (see below).
Example
cam:draw(function() love.graphics.rectangle('fill', -100,-100, 200,200) end)
function camera:predraw()^ top Apply camera transformations to every drawing operation until the next camera:postdraw().
function camera:postdraw()^ top Revert camera transformations for the rest of the drawing operations.
Example:(equivalent to the cam:draw() example above)
camera:predraw()
love.graphics.rectangle('fill', -100,-100, 200,200)
camera:postdraw()
function camera:toCameraCoords(p)^ top Transform vector p from world coordinates to camera coordinates, i.e. you have the position of an object in the world and want to know where it will be drawn on the screen.
Parameters:[vector]p: Vector to transform to camera coordinates
Returns:[vector] transformed vector.
function camera:toWorldCoords(p)^ top Transform vector p from camera coordinates to world coordinates, i.e. you have the coordinates of a point on the screen (e.g. the mouse), and wan't to know where it's location is in the world.
Parameters:[vector]p: Vector to transform to world coordinates
Returns:[vector] transformed vector.
function camera:mousepos()^ top Get mouse position in world coordinates, i.e. the position the users mouse is currently pointing at when camera transformations are applied. Use this for any mouse interaction with transformed objects in your game.
Returns:[vector] Mouse position in world coordinates.

ringbuffer.lua^ top

view source

A circular container that can hold arbitrary items. It can be used to create Tomb Raider style inventories, a looping music playlist, recurring dialog sequences and much more.

Accessors:

[table]buffer.itemsItems table. It is not recommended to read or write this directly.
[number]buffer.currentIndex of current selection. Be careful with this.

ringbuffer.lua defines the following functions:

Ringbuffer(...)Create new ringbuffer
ringbuffer:insert(item, ...)Insert items
ringbuffer:append(item, ...)Append items to the "end"
ringbuffer:remove()Remove item
ringbuffer:removeAt(k)Remove item at position
ringbuffer:get()Get item
ringbuffer:size()Number of items in buffer
ringbuffer:next()Get next item
ringbuffer:prev()Get previous item

function Ringbuffer(...)^ top Create a new ringbuffer.
Parameters[optional]...:Initial items of the buffer.
function ringbuffer:insert(item, ...)^ top Insert items behind current item.
Parameters:item:Item to insert
[optional]...:Additional items to insert
Example
rb = Ringbuffer(1,2,5,6) -- rb = {1 ,2,5,6}, rb:get() == 1
rb:next() -- rb = {1, 2 ,5,6}, rb:get() == 2
rb:insert(3,4) -- rb = {1, 2 ,3,4,5,6}, rb:get() == 2
rb:next() -- rb = {1,2, 3 ,4,5,6}, rb:get() == 3
function ringbuffer:append(item, ...)^ top Append items to ringbuffer.

This is less intuitive than ringbuffer:insert(item, ...) since it appears the items are added at a random location. Use is only recommended before using ringbuffer:next() or ringbuffer:prev().

Parameters:item:Item to append
[optional]...:Additional items to append
Example
rb = Ringbuffer(1,2,5,6) -- rb = {1 ,2,5,6}, rb:get() == 1
rb:next() -- rb = {1, 2 ,5,6}, rb:get() == 2
rb:append(3,4) -- rb = {1, 2 ,5,6,3,4}, rb:get() == 2
rb:next() -- rb = {1,2, 5 ,6,3,4}, rb:get() == 5
function ringbuffer:remove()^ top Remove currently selected item. The next item will be selected.
Example
rb = Ringbuffer(1,2,3) -- rb = {1 ,2,3}, rb:get() == 1
rb:next() -- rb = {1, 2 ,3}, rb:get() == 2
rb:remove() -- rb = {1, 3 }, rb:get() == 3
function ringbuffer:removeAt(k)^ top Remove item at relative position k. k may be positive or negative and even bigger than ringbuffer:size().
Parameters:[number]kRelative position of item to remove.
Example:
rb = Ringbuffer(1,2,3,4,5) -- rb = { 1 ,2,3,4,5}, rb:get() == 1
rb:removeAt(2) -- rb = { 1 ,2,4,5}, rb:get() == 1
rb:next() -- rb = {1, 2 ,4,5}, rb:get() == 2
rb:removeAt(-1) -- rb = { 2 ,4,5}, rb:get() == 2
function ringbuffer:get()^ top Get currently selected item.
Returns: The currently selected item.
function ringbuffer:size()^ top
Returns: Number of items in the buffer.
function ringbuffer:next()^ top Select next item and return it.
Return: Selected item after operation.
function ringbuffer:prev()^ top Select previous item and return it.
Return: Selected item after operation.

sequence.lua^ top

view source Depends on ringbuffer.lua

sequence.lua offers basic support for automated switching of Scenes. You can use it to add intros and cutscenes to your games.

sequence.lua defines two types: Scene and Sequence. A Sequence holds several Scenes, but one Scene can belong to different Sequences (but due to side effects it shouldn't).

The module consists of the following:

Sequence(...)Create new sequence
sequence:add(...)Add scenes
sequence:select(k)Select scene manually
sequence:rewind()Rewind sequence
sequence:prevSceneManually select previous scene
sequence:nextSceneManually select next scene
sequence:draw()Draw current scene
sequence:update(dt)Update and switch current scene
Scene(length)New scene object
scene:isFinished()Indicate end of scene

A Scene defines three callback functions. Do not try to access them yourself. Let Sequence do the job:

scene:draw():Draw contents on screen (mandatory)
scene:update(dt):Update scene (optional)
scene:enter():Called when entering scene (optional)
scene:leave():Called when leaving scene (optional)

Additional to that, each Scene has a time variable corresponding to the time spent in the scene.

The example shows some of the concepts involved.

function Sequence(...)^ top Create a new sequence with initial scenes.
Parameters:[optional Scenes]... Initial scenes to add to sequence
Returns:Sequence object with initial scenes
function sequence:add(...)^ top Append scenes to the end of the sequence.
Parameters:[Scenes]... Scenes to add to sequence
function sequence:select(k)^ top Manually selection of scene k. Usually this is not necessary but provided anyway.
Parameters: [number] k: Number of the scene to select
function sequence:rewind()^ top Select first scene in sequence.
function sequence:prevScene()^ top Manually select previous scene. Usually not necessay but provided anyway.

Warning: May select the last scene when the current scene is the first scene in sequence.

function sequence:nextScene()^ top Manually select next scene. Usually not necessay but provided anyway.

Warning: May select the first scene when the current scene is the last scene in sequence.

function sequence:draw()^ top Draws current scene.

Calls scene:draw() on currently active scene

function sequence:update(dt)^ top Update current scene and increase scene.time.

Switches scene if current scene is finished (see scene:isFinished()).

function Scene(length)^ top Create a new scene of length length
Parameters: [optional]length Length of scene. If < 0 scene will never finish. Defaults to -1
Returns: New scene object
^ top Indicates that the scene is finished. Used by sequence:update(dt).

This can be overwritten, though it is not advised to do so.

Returns: true if the scene is finished.

Example usage

Ending credits^ top
local sc = {}
-- moving and fading looks cool
sc.idea = Scene(5)
function sc.idea:draw()
    local frac = (self.time / self.length) ^ 3
    love.graphics.setColor(255,255,255,160 - frac * 160)
    love.graphics.print("Idea", 200 + frac * 30, 200)
    love.graphics.setColor(255,255,255,255 - frac * 255)
    love.graphics.print("Max Power", 300 - frac * 40, 250)
end

sc.code = Scene(5)
function sc.code:draw()
    local frac = (self.time / self.length) ^ 3
    love.graphics.setColor(255,255,255,160 - frac * 160)
    love.graphics.print("Code", 200 + frac * 30, 200)
    love.graphics.setColor(255,255,255,255 - frac * 255)
    love.graphics.print("Max Power", 300 - frac * 40, 250)
end

sc.gfx = Scene(5)
function sc.gfx:draw()
    local frac = (self.time / self.length) ^ 3
    love.graphics.setColor(255,255,255,160 - frac * 160)
    love.graphics.print("GFX", 200 + frac * 30, 200)
    love.graphics.setColor(255,255,255,255 - frac * 255)
    love.graphics.print("also Max Power", 300 - frac * 40, 250)
end

sc.endscreen = Scene(-1) -- last scene. no looping
function sc.endscreen:enter()
    self.frac = 0
end

function sc.endscreen:update(dt)
    self.frac = math.min(1, self.time / 3) -- 3 sec fade in
end

function sc.endscreen:draw()
    love.graphics.setColor(255,255,255, self.frac * 255)
    love.graphics.print("The most awesome game in history", 280, 280)
end


-- create credit sequence
credits = Sequence(sc.idea, sc.code, sc.gfx)
credits:add(sc.endscreen)


-- show and update sequence
function love.draw()
    credits:draw()
end

function love.update(dt)
    credits:update(dt)
end

License^ top

Yay, free software:

Copyright (c) 2010 Matthias Richter

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

Except as contained in this notice, the name(s) of the above copyright holders shall not be used in advertising or otherwise to promote the sale, use or other dealings in this Software without prior written authorization.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Download^ top

You can view and download the individual modules on github: vrld/hump. You may also download the whole packed sourcecode either in zip or tar formats.

You can clone the project with Git by running:

git clone git://github.com/vrld/hump
Once done, tou can check for updates by running
git pull