├── .gitignore ├── LICENSE ├── Missile.lua ├── Player.lua ├── README.md ├── assets ├── SuperMario256.ttf ├── attribution.txt ├── gamecontrollerdb.txt ├── gfx │ ├── hero.png │ └── missile.png └── sfx │ └── hit01.wav ├── conf.lua ├── lib ├── Animation.lua ├── Class.lua ├── Entity.lua ├── EntityMgr.lua ├── Events.lua ├── GamepadMgr.lua ├── Keyboard.lua ├── Rect.lua ├── Sat.lua ├── Scene.lua ├── SceneMgr.lua ├── Tween.lua ├── Utils.lua ├── Vector2.lua ├── Vector3.lua ├── components │ ├── Sprite.lua │ ├── StateMachine.lua │ ├── Transform.lua │ └── physics │ │ ├── CircleCollider.lua │ │ └── PolygonCollider.lua └── ui │ ├── Bar.lua │ ├── Button.lua │ ├── Checkbox.lua │ ├── Label.lua │ ├── Slider.lua │ └── TextField.lua ├── main.lua ├── scenes ├── MainMenu.lua ├── Test.lua └── TweenTest.lua └── test.lua /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Pixelbyte Studios 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Missile.lua: -------------------------------------------------------------------------------- 1 | local Class = require("lib.Class") 2 | local Anim = require("lib.Animation") 3 | local Vector2 = require("lib.Vector2") 4 | local Vector3 = require("lib.Vector3") 5 | 6 | local Sprite = require("lib.components.Sprite") 7 | local Transform = require("lib.components.Transform") 8 | 9 | local M = Class:derive("Missile") 10 | 11 | local missile_atlas 12 | local target_object 13 | local rotate_speed = 100 14 | local missile_speed = 40 15 | 16 | --Animation data 17 | local idle = Anim(0,0, 124, 80, 2, 2, 6 ) 18 | 19 | function M:new() 20 | self.vx = 0 21 | end 22 | 23 | function M.create_sprite() 24 | if missile_atlas == nil then 25 | missile_atlas = love.graphics.newImage("assets/gfx/missile.png") 26 | end 27 | local spr = Sprite(missile_atlas, 124, 80) 28 | spr:add_animations({idle = idle}) 29 | spr:animate("idle") 30 | return spr 31 | end 32 | 33 | function M:on_start() 34 | self.transform = self.entity.Transform 35 | end 36 | 37 | --Sets the target object 38 | --- 39 | function M:target(object) 40 | target_transform = object 41 | end 42 | 43 | function M:update(dt) 44 | 45 | if target_transform ~= nil then 46 | local missile_to_target = Vector2.sub(target_transform:VectorPos(), self.transform:VectorPos()) 47 | missile_to_target:unit() 48 | --print(missile_to_target.x .. " " .. missile_to_target.y ) 49 | 50 | local missile_dir = Vector2( math.cos(self.transform.angle ), math.sin(self.transform.angle)) 51 | missile_dir:unit() 52 | 53 | -- print("to target: " .. missile_to_target.x .. "," .. missile_to_target.y .. " missile dir: " .. missile_dir.x .. "," .. missile_dir.y ) 54 | local cp = Vector3.cross(missile_dir, missile_to_target) 55 | if cp.z < 0.005 and ( missile_to_target.x == -missile_dir.x or missile_to_target.y == -missile_dir.y) then cp.z = 10 end 56 | 57 | -- print(cp.x .. " " .. cp.y .. " " .. cp.z) 58 | self.transform.angle = self.transform.angle + cp.z * rotate_speed * (math.pi / 180) * dt 59 | self.transform.x = self.transform.x + (missile_dir.x * missile_speed * dt) 60 | self.transform.y = self.transform.y + (missile_dir.y * missile_speed * dt) 61 | end 62 | end 63 | 64 | return M -------------------------------------------------------------------------------- /Player.lua: -------------------------------------------------------------------------------- 1 | local Class = require("lib.Class") 2 | local Anim = require("lib.Animation") 3 | 4 | local Sprite = require("lib.components.Sprite") 5 | local Transform = require("lib.components.Transform") 6 | local StateMachine = require("lib.components.StateMachine") 7 | 8 | 9 | local P = Class:derive("Player") 10 | 11 | local hero_atlas 12 | local snd 13 | 14 | --Animation data 15 | local idle = Anim(16, 16, 16, 16, 4, 4, 6 ) 16 | local walk = Anim(16, 32, 16, 16, 6, 6, 12) 17 | local jump = Anim(16, 48, 16, 16, 1, 1, 10, false) 18 | local swim = Anim(16, 64, 16, 16, 6, 6, 12) 19 | local punch = Anim(16, 80, 16, 16, 3, 3, 10, false) 20 | 21 | function P:new() 22 | if snd == nil then 23 | snd = love.audio.newSource("assets/sfx/hit01.wav", "static") 24 | end 25 | 26 | self.vx = 0 27 | self.machine = StateMachine(self, "idle") 28 | end 29 | 30 | function P.create_sprite() 31 | if hero_atlas == nil then 32 | hero_atlas = love.graphics.newImage("assets/gfx/hero.png") 33 | end 34 | 35 | --create a sprite component 36 | local sprite = Sprite(hero_atlas, 16, 16) 37 | sprite:add_animations({idle = idle, walk = walk, swim = swim, punch = punch, jump = jump}) 38 | sprite:animate("idle") 39 | return sprite 40 | end 41 | 42 | function P:on_start() 43 | self.transform = self.entity.Transform 44 | self.sprite = self.entity.Sprite 45 | end 46 | 47 | function P:idle_enter(dt) 48 | self.sprite:animate("idle") 49 | end 50 | 51 | function P:idle(dt) 52 | if Key:key("left") or Key:key("right") then 53 | self.machine:change("walk") 54 | elseif Key:key_down("space") then 55 | self.machine:change("punch") 56 | elseif Key:key_down("z") then 57 | self.machine:change("jump") 58 | end 59 | end 60 | 61 | function P:punch_enter(dt) 62 | self.sprite:animate("punch") 63 | love.audio.stop(snd) 64 | love.audio.play(snd) 65 | end 66 | 67 | function P:punch(dt) 68 | if self.sprite:animation_finished() then 69 | self.machine:change("idle") 70 | end 71 | end 72 | 73 | local jumping = false 74 | local y_before_jump = nil 75 | 76 | function P:jump_enter(dt) 77 | jumping = true 78 | self.sprite:animate("jump") 79 | end 80 | 81 | function P:jump(dt) 82 | if not jumping then 83 | self.machine:change("idle") 84 | y_before_jump = nil 85 | elseif Key:key_down("space") then 86 | self.machine:change("punch") 87 | end 88 | end 89 | 90 | function P:walk_enter(dt) 91 | self.sprite:animate("walk") 92 | end 93 | 94 | function P:walk(dt) 95 | 96 | if Key:key("right") and not Key:key("left") and vx ~= 1 then 97 | self.sprite:flip_h(false) 98 | self.vx = 1 99 | elseif Key:key("left") and not Key:key("right") and vx ~= -1 then 100 | self.sprite:flip_h(true) 101 | self.vx = -1 102 | elseif not Key:key("left") and not Key:key("right") then 103 | self.vx = 0 104 | self.machine:change("idle") 105 | end 106 | if Key:key_down("space") then 107 | self.vx = 0 108 | self.machine:change("punch") 109 | elseif Key:key_down("z") then 110 | self.machine:change("jump") 111 | end 112 | end 113 | 114 | local y_vel = 0 115 | local y_gravity = 1000 116 | function P:update(dt) 117 | self.machine:update(dt) 118 | self.transform.x = self.transform.x + self.vx * 115 * dt 119 | 120 | if Key:key("up") then 121 | self.transform.y = self.transform.y - 115 * dt 122 | elseif Key:key("down") then 123 | self.transform.y = self.transform.y + 115 * dt 124 | end 125 | 126 | if jumping and y_before_jump == nil then 127 | y_vel = -400 128 | y_before_jump = self.transform.y 129 | elseif jumping then 130 | y_vel = y_vel + y_gravity * dt 131 | self.transform.y = self.transform.y + y_vel * dt 132 | 133 | if self.transform.y >= y_before_jump then 134 | jumping = false 135 | self.transform.y = y_before_jump 136 | y_before_jump = nil 137 | self.vx = 0 138 | self.machine:change("idle") 139 | end 140 | end 141 | end 142 | 143 | --This function responds to a collision event on any of the 144 | -- given sides of the player's collision rect 145 | -- top, bottom, left, right are all boolean values 146 | function P:collided(top, bottom, left, right) 147 | if bottom then 148 | jumping = false 149 | y_before_jump = nil 150 | self.vx = 0 151 | self.machine:change("idle") 152 | end 153 | end 154 | 155 | return P -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # love2d-tutorial 2 | Code from my Love2D tutorial series which can be found [HERE](https://www.youtube.com/playlist?list=PLZVNxI_lsRW2kXnJh2BMb6D82HCAoSTUB) 3 | 4 | ## Note 5 | Code from each episode is tagged with its episode number 6 | -------------------------------------------------------------------------------- /assets/SuperMario256.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bncastle/love2d-tutorial/b2294e45bb6580e3bb327ce4429a53ffcde37840/assets/SuperMario256.ttf -------------------------------------------------------------------------------- /assets/attribution.txt: -------------------------------------------------------------------------------- 1 | CC0 graphics 2 | ======== 3 | hero.png: http://opengameart.org/content/classic-hero 4 | 5 | CC0 Sounds 6 | ===== 7 | hit01.wav: http://opengameart.org/content/37-hitspunches -------------------------------------------------------------------------------- /assets/gamecontrollerdb.txt: -------------------------------------------------------------------------------- 1 | # Windows - DINPUT 2 | 8f0e1200000000000000504944564944,Acme,platform:Windows,x:b2,a:b0,b:b1,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b6,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2, 3 | 341a3608000000000000504944564944,Afterglow PS3 Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, 4 | ffff0000000000000000504944564944,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows, 5 | 6d0416c2000000000000504944564944,Generic DirectInput Controller,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, 6 | 0d0f6e00000000000000504944564944,HORIPAD 4,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows, 7 | 6d0419c2000000000000504944564944,Logitech F710 Gamepad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Windows, 8 | 88880803000000000000504944564944,PS3 Controller,a:b2,b:b1,back:b8,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b9,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:b7,rightx:a3,righty:a4,start:b11,x:b0,y:b3,platform:Windows, 9 | 4c056802000000000000504944564944,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Windows, 10 | 25090500000000000000504944564944,PS3 DualShock,a:b2,b:b1,back:b9,dpdown:h0.8,dpleft:h0.4,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b6,leftstick:b10,lefttrigger:b4,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b11,righttrigger:b5,rightx:a2,righty:a3,start:b8,x:b0,y:b3,platform:Windows, 11 | 4c05c405000000000000504944564944,Sony DualShock 4,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Windows, 12 | 4c05cc09000000000000504944564944,Sony DualShock 4,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Windows, 13 | 4c05a00b000000000000504944564944,Sony DualShock 4 Wireless Adaptor,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Windows, 14 | 6d0418c2000000000000504944564944,Logitech RumblePad 2 USB,platform:Windows,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3, 15 | 36280100000000000000504944564944,OUYA Controller,platform:Windows,a:b0,b:b3,y:b2,x:b1,start:b14,guide:b15,leftstick:b6,rightstick:b7,leftshoulder:b4,rightshoulder:b5,dpup:b8,dpleft:b10,dpdown:b9,dpright:b11,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:b12,righttrigger:b13, 16 | 4f0400b3000000000000504944564944,Thrustmaster Firestorm Dual Power,a:b0,b:b2,y:b3,x:b1,start:b10,guide:b8,back:b9,leftstick:b11,rightstick:b12,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b5,righttrigger:b7,platform:Windows, 17 | 00f00300000000000000504944564944,RetroUSB.com RetroPad,a:b1,b:b5,x:b0,y:b4,back:b2,start:b3,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Windows, 18 | 00f0f100000000000000504944564944,RetroUSB.com Super RetroPort,a:b1,b:b5,x:b0,y:b4,back:b2,start:b3,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Windows, 19 | 28040140000000000000504944564944,GamePad Pro USB,platform:Windows,a:b1,b:b2,x:b0,y:b3,back:b8,start:b9,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,lefttrigger:b6,righttrigger:b7, 20 | ff113133000000000000504944564944,SVEN X-PAD,platform:Windows,a:b2,b:b3,y:b1,x:b0,start:b5,back:b4,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a4,lefttrigger:b8,righttrigger:b9, 21 | 8f0e0300000000000000504944564944,Piranha xtreme,platform:Windows,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2, 22 | 8f0e0d31000000000000504944564944,Multilaser JS071 USB,platform:Windows,a:b1,b:b2,y:b3,x:b0,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7, 23 | 10080300000000000000504944564944,PS2 USB,platform:Windows,a:b2,b:b1,y:b0,x:b3,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a4,righty:a2,lefttrigger:b4,righttrigger:b5, 24 | 79000600000000000000504944564944,G-Shark GS-GP702,a:b2,b:b1,x:b3,y:b0,back:b8,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a4,lefttrigger:b6,righttrigger:b7,platform:Windows, 25 | 4b12014d000000000000504944564944,NYKO AIRFLO,a:b0,b:b1,x:b2,y:b3,back:b8,guide:b10,start:b9,leftstick:a0,rightstick:a2,leftshoulder:a3,rightshoulder:b5,dpup:h0.1,dpdown:h0.0,dpleft:h0.8,dpright:h0.2,leftx:h0.6,lefty:h0.12,rightx:h0.9,righty:h0.4,lefttrigger:b6,righttrigger:b7,platform:Windows, 26 | d6206dca000000000000504944564944,PowerA Pro Ex,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.0,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows, 27 | a3060cff000000000000504944564944,Saitek P2500,a:b2,b:b3,y:b1,x:b0,start:b4,guide:b10,back:b5,leftstick:b8,rightstick:b9,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,platform:Windows, 28 | 4f0415b3000000000000504944564944,Thrustmaster Dual Analog 3.2,platform:Windows,x:b1,a:b0,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b6,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3, 29 | 6f0e1e01000000000000504944564944,Rock Candy Gamepad for PS3,platform:Windows,a:b1,b:b2,x:b0,y:b3,back:b8,start:b9,guide:b12,leftshoulder:b4,rightshoulder:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2, 30 | 83056020000000000000504944564944,iBuffalo USB 2-axis 8-button Gamepad,a:b1,b:b0,y:b2,x:b3,start:b7,back:b6,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,platform:Windows, 31 | 10080100000000000000504944564944,PS1 USB,platform:Windows,a:b2,b:b1,x:b3,y:b0,back:b8,start:b9,leftshoulder:b6,rightshoulder:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b4,righttrigger:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2, 32 | 49190204000000000000504944564944,Ipega PG-9023,a:b0,b:b1,x:b3,y:b4,back:b10,start:b11,leftstick:b13,rightstick:b14,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:b8,righttrigger:b9,platform:Windows, 33 | 4f0423b3000000000000504944564944,Dual Trigger 3-in-1,a:b1,b:b2,x:b0,y:b3,back:b8,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:b6,righttrigger:b7,platform:Windows, 34 | 0d0f4900000000000000504944564944,Hatsune Miku Sho Controller,a:b1,b:b2,x:b0,y:b3,back:b8,guide:b12,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows, 35 | 79004318000000000000504944564944,Mayflash GameCube Controller Adapter,platform:Windows,a:b1,b:b2,x:b0,y:b3,back:b0,start:b9,guide:b0,leftshoulder:b4,rightshoulder:b7,leftstick:b0,rightstick:b0,leftx:a0,lefty:a1,rightx:a5,righty:a2,lefttrigger:a3,righttrigger:a4,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2, 36 | 79000018000000000000504944564944,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b1,b:b2,x:b0,y:b3,back:b8,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows, 37 | 2509e803000000000000504944564944,Mayflash Wii Classic Controller,a:b1,b:b0,x:b3,y:b2,back:b8,guide:b10,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:b11,dpdown:b13,dpleft:b12,dpright:b14,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Windows, 38 | 300f1001000000000000504944564944,Saitek P480 Rumble Pad,a:b2,b:b3,x:b0,y:b1,back:b8,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b5,righttrigger:b7,platform:Windows, 39 | 10280900000000000000504944564944,8Bitdo SFC30 GamePad,a:b1,b:b0,y:b3,x:b4,start:b11,back:b10,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,platform:Windows, 40 | 63252305000000000000504944564944,USB Vibration Joystick (BM),platform:Windows,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3, 41 | 20380900000000000000504944564944,8Bitdo NES30 PRO Wireless,platform:Windows,a:b0,b:b1,x:b3,y:b4,leftshoulder:b6,rightshoulder:b7,lefttrigger:b8,righttrigger:b9,back:b10,start:b11,leftstick:b13,rightstick:b14,leftx:a0,lefty:a1,rightx:a3,righty:a4,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8, 42 | 02200090000000000000504944564944,8Bitdo NES30 PRO USB,platform:Windows,a:b0,b:b1,x:b3,y:b4,leftshoulder:b6,rightshoulder:b7,lefttrigger:b8,righttrigger:b9,back:b10,start:b11,leftstick:b13,rightstick:b14,leftx:a0,lefty:a1,rightx:a3,righty:a4,dpup:h0.1,dpright:h0.2,dpdown:h0.4,dpleft:h0.8, 43 | ff113133000000000000504944564944,Gembird JPD-DualForce,platform:Windows,a:b2,b:b3,x:b0,y:b1,start:b9,back:b8,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a4,lefttrigger:b6,righttrigger:b7,leftstick:b10,rightstick:b11, 44 | 341a0108000000000000504944564944,EXEQ RF USB Gamepad 8206,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,leftstick:b8,rightstick:b7,back:b8,start:b9,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftx:a0,lefty:a1,rightx:a2,righty:a3,platform:Windows, 45 | c0111352000000000000504944564944,Battalife Joystick,platform:Windows,x:b4,a:b6,b:b7,y:b5,back:b2,start:b3,leftshoulder:b0,rightshoulder:b1,leftx:a0,lefty:a1, 46 | 100801e5000000000000504944564944,NEXT Classic USB Game Controller,a:b0,b:b1,back:b8,start:b9,rightx:a2,righty:a3,leftx:a0,lefty:a1,platform:Windows, 47 | 79000600000000000000504944564944,NGS Phantom,a:b2,b:b3,y:b1,x:b0,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a4,lefttrigger:b6,righttrigger:b7,platform:Windows, 48 | 49 | # OS X 50 | 0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Mac OS X, 51 | 6d0400000000000016c2000000000000,Logitech F310 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, 52 | 6d0400000000000018c2000000000000,Logitech F510 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, 53 | 6d040000000000001fc2000000000000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, 54 | 6d0400000000000019c2000000000000,Logitech Wireless Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Mac OS X, 55 | 4c050000000000006802000000000000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Mac OS X, 56 | 4c05000000000000c405000000000000,Sony DualShock 4,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Mac OS X, 57 | 4c05000000000000cc09000000000000,Sony DualShock 4 V2,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Mac OS X, 58 | 5e040000000000008e02000000000000,X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, 59 | 891600000000000000fd000000000000,Razer Onza Tournament,a:b0,b:b1,y:b3,x:b2,start:b8,guide:b10,back:b9,leftstick:b6,rightstick:b7,leftshoulder:b4,rightshoulder:b5,dpup:b11,dpleft:b13,dpdown:b12,dpright:b14,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Mac OS X, 60 | 4f0400000000000000b3000000000000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,y:b3,x:b1,start:b10,guide:b8,back:b9,leftstick:b11,rightstick:,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b5,righttrigger:b7,platform:Mac OS X, 61 | 8f0e0000000000000300000000000000,Piranha xtreme,platform:Mac OS X,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2, 62 | 0d0f0000000000004d00000000000000,HORI Gem Pad 3,platform:Mac OS X,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7, 63 | 79000000000000000600000000000000,G-Shark GP-702,a:b2,b:b1,x:b3,y:b0,back:b8,start:b9,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:b6,righttrigger:b7,platform:Mac OS X, 64 | 4f0400000000000015b3000000000000,Thrustmaster Dual Analog 3.2,platform:Mac OS X,x:b1,a:b0,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b6,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3, 65 | AD1B00000000000001F9000000000000,Gamestop BB-070 X360 Controller,a:b0,b:b1,back:b9,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b10,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b8,x:b2,y:b3,platform:Mac OS X, 66 | 050000005769696d6f74652028303000,Wii Remote,a:b4,b:b5,y:b9,x:b10,start:b6,guide:b8,back:b7,dpup:b2,dpleft:b0,dpdown:b3,dpright:b1,leftx:a0,lefty:a1,lefttrigger:b12,righttrigger:,leftshoulder:b11,platform:Mac OS X, 67 | 83050000000000006020000000000000,iBuffalo USB 2-axis 8-button Gamepad,a:b1,b:b0,x:b3,y:b2,back:b6,start:b7,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,platform:Mac OS X, 68 | bd1200000000000015d0000000000000,Tomee SNES USB Controller,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,platform:Mac OS X, 69 | 79000000000000001100000000000000,Retrolink Classic Controller,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,leftshoulder:b4,rightshoulder:b5,leftx:a3,lefty:a4,platform:Mac OS X, 70 | 5e04000000000000dd02000000000000,Xbox One Wired Controller,platform:Mac OS X,x:b2,a:b0,b:b1,y:b3,back:b9,guide:b10,start:b8,dpleft:b13,dpdown:b12,dpright:b14,dpup:b11,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b6,rightstick:b7,leftx:a0,lefty:a1,rightx:a3,righty:a4, 71 | 5e04000000000000ea02000000000000,Xbox Wireless Controller,platform:Mac OS X,x:b2,a:b0,b:b1,y:b3,back:b9,guide:b10,start:b8,dpleft:b13,dpdown:b12,dpright:b14,dpup:b11,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b6,rightstick:b7,leftx:a0,lefty:a1,rightx:a3,righty:a4, 72 | 5e04000000000000e002000000000000,Xbox Wireless Controller,platform:Mac OS X,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b10,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a3,righty:a4, 73 | 050000005769696d6f74652028313800,Wii U Pro Controller,a:b16,b:b15,x:b18,y:b17,back:b7,guide:b8,start:b6,leftstick:b23,rightstick:b24,leftshoulder:b19,rightshoulder:b20,dpup:b11,dpdown:b12,dpleft:b13,dpright:b14,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b21,righttrigger:b22,platform:Mac OS X, 74 | 79000000000000000018000000000000,Mayflash WiiU Pro Game Controller Adapter (DInput),a:b4,b:b8,x:b0,y:b12,back:b32,start:b36,leftstick:b40,rightstick:b44,leftshoulder:b16,rightshoulder:b20,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftx:a0,lefty:a4,rightx:a8,righty:a12,lefttrigger:b24,righttrigger:b28,platform:Mac OS X, 75 | 2509000000000000e803000000000000,Mayflash Wii Classic Controller,a:b1,b:b0,x:b3,y:b2,back:b8,guide:b10,start:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:b11,dpdown:b13,dpleft:b12,dpright:b14,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Mac OS X, 76 | 351200000000000021ab000000000000,SFC30 Joystick,a:b1,b:b0,x:b4,y:b3,back:b10,start:b11,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Mac OS X, 77 | b4040000000000000a01000000000000,Sega Saturn USB Gamepad,a:b0,b:b1,x:b3,y:b4,back:b5,guide:b2,start:b8,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Mac OS X, 78 | 81170000000000007e05000000000000,Sega Saturn,x:b0,a:b2,b:b4,y:b6,start:b13,dpleft:b15,dpdown:b16,dpright:b14,dpup:b17,leftshoulder:b8,lefttrigger:a5,lefttrigger:b10,rightshoulder:b9,righttrigger:a4,righttrigger:b11,leftx:a0,lefty:a2,platform:Mac OS X, 79 | 10280000000000000900000000000000,8Bitdo SFC30 GamePad,a:b1,b:b0,x:b4,y:b3,back:b10,start:b11,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Mac OS X, 80 | d814000000000000cecf000000000000,MC Cthulhu,platform:Mac OS X,leftx:,lefty:,rightx:,righty:,lefttrigger:b6,a:b1,b:b2,y:b3,x:b0,start:b9,back:b8,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,righttrigger:b7, 81 | 0d0f0000000000006600000000000000,HORIPAD FPS PLUS 4,platform:Mac OS X,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:b6,righttrigger:a4, 82 | 83 | # Linux 84 | 0500000047532047616d657061640000,GameStop Gamepad,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux, 85 | 03000000ba2200002010000001010000,Jess Technology USB Game Controller,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b3,y:b0,platform:Linux, 86 | 030000006d04000019c2000010010000,Logitech Cordless RumblePad 2,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, 87 | 030000006d0400001dc2000014400000,Logitech F310 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, 88 | 030000006d04000016c2000011010000,Logitech F310 Gamepad (DInput),x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,platform:Linux, 89 | 030000006d0400001ec2000020200000,Logitech F510 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, 90 | 030000006d04000019c2000011010000,Logitech F710 Gamepad (DInput),a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,platform:Linux, 91 | 030000006d0400001fc2000005030000,Logitech F710 Gamepad (XInput),a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, 92 | 030000004c0500006802000011010000,PS3 Controller,a:b14,b:b13,back:b0,dpdown:b6,dpleft:b7,dpright:b5,dpup:b4,guide:b16,leftshoulder:b10,leftstick:b1,lefttrigger:b8,leftx:a0,lefty:a1,rightshoulder:b11,rightstick:b2,righttrigger:b9,rightx:a2,righty:a3,start:b3,x:b15,y:b12,platform:Linux, 93 | 030000004c050000c405000011010000,Sony DualShock 4,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Linux, 94 | 050000004c050000c405000000010000,Sony DualShock 4 BT,a:b1,b:b2,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,platform:Linux, 95 | 030000004c050000cc09000011010000,Sony DualShock 4 V2,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Linux, 96 | 050000004c050000cc09000000010000,Sony DualShock 4 V2 BT,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Linux, 97 | 030000004c050000a00b000011010000,Sony DualShock 4 Wireless Adaptor,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b13,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:a3,righttrigger:a4,platform:Linux, 98 | 030000006f0e00003001000001010000,EA Sports PS3 Controller,platform:Linux,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7, 99 | 03000000de280000ff11000001000000,Valve Streaming Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, 100 | 030000005e0400008e02000014010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, 101 | 030000005e0400008e02000010010000,X360 Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, 102 | 030000005e0400001907000000010000,X360 Wireless Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux, 103 | 03000000100800000100000010010000,Twin USB PS2 Adapter,a:b2,b:b1,y:b0,x:b3,start:b9,guide:,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b4,righttrigger:b5,platform:Linux, 104 | 03000000a306000023f6000011010000,Saitek Cyborg V.1 Game Pad,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a4,lefttrigger:b6,righttrigger:b7,platform:Linux, 105 | 030000004f04000020b3000010010000,Thrustmaster 2 in 1 DT,a:b0,b:b2,y:b3,x:b1,start:b9,guide:,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b5,righttrigger:b7,platform:Linux, 106 | 030000004f04000023b3000000010000,Thrustmaster Dual Trigger 3-in-1,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a5, 107 | 030000008f0e00000300000010010000,GreenAsia Inc. USB Joystick ,platform:Linux,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2, 108 | 030000008f0e00001200000010010000,GreenAsia Inc. USB Joystick ,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b5,rightshoulder:b6,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a2, 109 | 030000005e0400009102000007010000,X360 Wireless Controller,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:b13,dpleft:b11,dpdown:b14,dpright:b12,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Linux, 110 | 030000006d04000016c2000010010000,Logitech Logitech Dual Action,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3, 111 | 03000000260900008888000000010000,GameCube {WiseGroup USB box},a:b0,b:b2,y:b3,x:b1,start:b7,leftshoulder:,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,rightstick:,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a4,righttrigger:a5,platform:Linux, 112 | 030000006d04000011c2000010010000,Logitech WingMan Cordless RumblePad,a:b0,b:b1,y:b4,x:b3,start:b8,guide:b5,back:b2,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:b9,righttrigger:b10,platform:Linux, 113 | 030000006d04000018c2000010010000,Logitech Logitech RumblePad 2 USB,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3, 114 | 05000000d6200000ad0d000001000000,Moga Pro,platform:Linux,a:b0,b:b1,y:b3,x:b2,start:b6,leftstick:b7,rightstick:b8,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:a5,righttrigger:a4, 115 | 030000004f04000009d0000000010000,Thrustmaster Run N Drive Wireless PS3,platform:Linux,a:b1,b:b2,x:b0,y:b3,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7, 116 | 030000004f04000008d0000000010000,Thrustmaster Run N Drive Wireless,platform:Linux,a:b1,b:b2,x:b0,y:b3,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a5,lefttrigger:b6,righttrigger:b7, 117 | 0300000000f000000300000000010000,RetroUSB.com RetroPad,a:b1,b:b5,x:b0,y:b4,back:b2,start:b3,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Linux, 118 | 0300000000f00000f100000000010000,RetroUSB.com Super RetroPort,a:b1,b:b5,x:b0,y:b4,back:b2,start:b3,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1,platform:Linux, 119 | 030000006f0e00001f01000000010000,Generic X-Box pad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4, 120 | 03000000280400000140000000010000,Gravis GamePad Pro USB ,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftx:a0,lefty:a1, 121 | 030000005e0400008902000021010000,Microsoft X-Box pad v2 (US),platform:Linux,x:b3,a:b0,b:b1,y:b4,back:b6,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b5,lefttrigger:a2,rightshoulder:b2,righttrigger:a5,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a3,righty:a4, 122 | 030000005e0400008502000000010000,Microsoft X-Box pad (Japan),platform:Linux,x:b3,a:b0,b:b1,y:b4,back:b6,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b5,lefttrigger:a2,rightshoulder:b2,righttrigger:a5,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a3,righty:a4, 123 | 030000006f0e00001e01000011010000,Rock Candy Gamepad for PS3,platform:Linux,a:b1,b:b2,x:b0,y:b3,back:b8,start:b9,guide:b12,leftshoulder:b4,rightshoulder:b5,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2, 124 | 03000000250900000500000000010000,Sony PS2 pad with SmartJoy adapter,platform:Linux,a:b2,b:b1,y:b0,x:b3,start:b8,back:b9,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b4,righttrigger:b5, 125 | 030000008916000000fd000024010000,Razer Onza Tournament,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:b13,dpleft:b11,dpdown:b14,dpright:b12,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Linux, 126 | 030000004f04000000b3000010010000,Thrustmaster Firestorm Dual Power,a:b0,b:b2,y:b3,x:b1,start:b10,guide:b8,back:b9,leftstick:b11,rightstick:b12,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b5,righttrigger:b7,platform:Linux, 127 | 03000000ad1b000001f5000033050000,Hori Pad EX Turbo 2,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Linux, 128 | 060000004c0500006802000000010000,PS3 Controller (Bluetooth),a:b14,b:b13,y:b12,x:b15,start:b3,guide:b16,back:b0,leftstick:b1,rightstick:b2,leftshoulder:b10,rightshoulder:b11,dpup:b4,dpleft:b7,dpdown:b6,dpright:b5,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b8,righttrigger:b9,platform:Linux, 129 | 050000004c0500006802000000010000,PS3 Controller (Bluetooth),a:b14,b:b13,y:b12,x:b15,start:b3,guide:b16,back:b0,leftstick:b1,rightstick:b2,leftshoulder:b10,rightshoulder:b11,dpup:b4,dpleft:b7,dpdown:b6,dpright:b5,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b8,righttrigger:b9,platform:Linux, 130 | 03000000790000000600000010010000,DragonRise Inc. Generic USB Joystick ,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a3,rightx:a1,righty:a4, 131 | 03000000666600000488000000010000,Super Joy Box 5 Pro,platform:Linux,a:b2,b:b1,x:b3,y:b0,back:b9,start:b8,leftshoulder:b6,rightshoulder:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b4,righttrigger:b5,dpup:b12,dpleft:b15,dpdown:b14,dpright:b13, 132 | 05000000362800000100000002010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,platform:Linux,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2, 133 | 05000000362800000100000003010000,OUYA Game Controller,a:b0,b:b3,dpdown:b9,dpleft:b10,dpright:b11,dpup:b8,guide:b14,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,platform:Linux,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b1,y:b2, 134 | 030000008916000001fd000024010000,Razer Onza Classic Edition,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:b11,dpdown:b14,dpright:b12,dpup:b13,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4, 135 | 030000005e040000d102000001010000,Microsoft X-Box One pad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4, 136 | 030000005e040000dd02000003020000,Microsoft X-Box One pad v2,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,platform:Linux, 137 | 03000000790000001100000010010000,RetroLink Saturn Classic Controller,platform:Linux,x:b3,a:b0,b:b1,y:b4,back:b5,guide:b2,start:b8,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1, 138 | 050000007e0500003003000001000000,Nintendo Wii U Pro Controller,platform:Linux,a:b0,b:b1,x:b3,y:b2,back:b8,start:b9,guide:b10,leftshoulder:b4,rightshoulder:b5,leftstick:b11,rightstick:b12,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,dpup:b13,dpleft:b15,dpdown:b14,dpright:b16, 139 | 030000005e0400008e02000004010000,Microsoft X-Box 360 pad,platform:Linux,a:b0,b:b1,x:b2,y:b3,back:b6,start:b7,guide:b8,leftshoulder:b4,rightshoulder:b5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2, 140 | 030000000d0f00002200000011010000,HORI CO. LTD. REAL ARCADE Pro.V3,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1, 141 | 030000000d0f00001000000011010000,HORI CO. LTD. FIGHTING STICK 3,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7 142 | 03000000f0250000c183000010010000,Goodbetterbest Ltd USB Controller,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3, 143 | 0000000058626f782047616d65706100,Xbox Gamepad (userspace driver),platform:Linux,a:b0,b:b1,x:b2,y:b3,start:b7,back:b6,guide:b8,dpup:h0.1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,leftshoulder:b4,rightshoulder:b5,lefttrigger:a5,righttrigger:a4,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a2,righty:a3, 144 | 03000000ff1100003133000010010000,PC Game Controller,a:b2,b:b1,y:b0,x:b3,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7,platform:Linux, 145 | 030000005e0400008e02000020200000,SpeedLink XEOX Pro Analog Gamepad pad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4, 146 | 030000006f0e00001304000000010000,Generic X-Box pad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:a0,rightstick:a3,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4, 147 | 03000000a306000018f5000010010000,Saitek PLC Saitek P3200 Rumble Pad,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,start:b9,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a3,righty:a4, 148 | 03000000830500006020000010010000,iBuffalo USB 2-axis 8-button Gamepad,a:b1,b:b0,x:b3,y:b2,back:b6,start:b7,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,platform:Linux, 149 | 03000000bd12000015d0000010010000,Tomee SNES USB Controller,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,platform:Linux, 150 | 03000000790000001100000010010000,Retrolink Classic Controller,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,platform:Linux, 151 | 03000000c9110000f055000011010000,HJC Game GAMEPAD,leftx:a0,lefty:a1,dpdown:h0.4,rightstick:b11,rightshoulder:b5,rightx:a2,start:b9,righty:a3,dpleft:h0.8,lefttrigger:b6,x:b2,dpup:h0.1,back:b8,leftstick:b10,leftshoulder:b4,y:b3,a:b0,dpright:h0.2,righttrigger:b7,b:b1,platform:Linux, 152 | 03000000a30600000c04000011010000,Saitek P2900 Wireless Pad,a:b1,b:b2,y:b3,x:b0,start:b12,guide:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b4,righttrigger:b5,platform:Linux, 153 | 03000000341a000005f7000010010000,GameCube {HuiJia USB box},a:b1,b:b2,y:b3,x:b0,start:b9,guide:,back:,leftstick:,rightstick:,leftshoulder:,dpleft:b15,dpdown:b14,dpright:b13,leftx:a0,lefty:a1,rightx:a5,righty:a2,lefttrigger:a3,righttrigger:a4,rightshoulder:b7,dpup:b12,platform:Linux, 154 | 030000006e0500000320000010010000,JC-U3613M - DirectInput Mode,platform:Linux,x:b0,a:b2,b:b3,y:b1,back:b10,guide:b12,start:b11,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b8,rightstick:b9,leftx:a0,lefty:a1,rightx:a2,righty:a3, 155 | 030000006f0e00004601000001010000,Rock Candy Wired Controller for Xbox One,platform:Linux,a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,guide:b8,leftstick:b9,rightstick:b10,lefttrigger:a2,righttrigger:a5,leftx:a0,lefty:a1,rightx:a3,righty:a4, 156 | 03000000380700001647000010040000,Mad Catz Wired Xbox 360 Controller,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4, 157 | 030000006f0e00003901000020060000,Afterglow Wired Controller for Xbox One,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4,platform:Linux, 158 | 030000004f04000015b3000010010000,Thrustmaster Dual Analog 4,platform:Linux,a:b0,b:b2,x:b1,y:b3,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b5,righttrigger:b7, 159 | 05000000102800000900000000010000,8Bitdo SFC30 GamePad,platform:Linux,x:b4,a:b1,b:b0,y:b3,back:b10,start:b11,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1, 160 | 03000000d81400000862000011010000,HitBox (PS3/PC) Analog Mode,platform:Linux,a:b1,b:b2,y:b3,x:b0,start:b12,guide:b9,back:b8,leftshoulder:b4,rightshoulder:b5,lefttrigger:b6,righttrigger:b7,leftx:a0,lefty:a1, 161 | 030000000d0f00000d00000000010000,hori,platform:Linux,a:b0,b:b6,y:b2,x:b1,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,start:b9,guide:b10,back:b8,leftshoulder:b3,rightshoulder:b7,leftx:b4,lefty:b5, 162 | 03000000ad1b000016f0000090040000,Mad Catz Xbox 360 Controller,platform:Linux,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5, 163 | 03000000d814000007cd000011010000,Toodles 2008 Chimp PC/PS3,platform:Linux,a:b0,b:b1,y:b2,x:b3,start:b9,back:b8,leftshoulder:b4,rightshoulder:b5,leftx:a0,lefty:a1,lefttrigger:b6,righttrigger:b7, 164 | 03000000fd0500000030000000010000,InterAct GoPad I-73000 (Fighting Game Layout),platform:Linux,a:b3,b:b4,y:b1,x:b0,start:b7,back:b6,leftx:a0,lefty:a1,rightshoulder:b2,righttrigger:b5, 165 | 05000000010000000100000003000000,Nintendo Wiimote,platform:Linux,a:b0,b:b1,y:b3,x:b2,start:b9,guide:b10,back:b8,leftstick:b11,rightstick:b12,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7, 166 | 030000005e0400008e02000062230000,Microsoft X-Box 360 pad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4, 167 | 03000000a30600000901000000010000,Saitek P880,a:b2,b:b3,y:b1,x:b0,leftstick:b8,rightstick:b9,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b6,righttrigger:b7,platform:Linux, 168 | 030000006f0e00000103000000020000,Logic3 Controller,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.0,dpdown:h0.4,dpright:h0.0,dpright:h0.2,dpup:h0.0,dpup:h0.1,leftshoulder:h0.0,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4, 169 | 05000000380700006652000025010000,Mad Catz C.T.R.L.R ,platform:Linux,x:b0,a:b1,b:b2,y:b3,back:b8,guide:b12,start:b9,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,leftstick:b10,rightstick:b11,leftx:a0,lefty:a1,rightx:a2,righty:a3, 170 | 030000005e0400008e02000073050000,Speedlink TORID Wireless Gamepad,platform:Linux,x:b2,a:b0,b:b1,y:b3,back:b6,guide:b8,start:b7,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,dpup:h0.1,leftshoulder:b4,lefttrigger:a2,rightshoulder:b5,righttrigger:a5,leftstick:b9,rightstick:b10,leftx:a0,lefty:a1,rightx:a3,righty:a4, 171 | 03000000ad1b00002ef0000090040000,Mad Catz Fightpad SFxT,platform:Linux,a:b0,b:b1,y:b3,x:b2,start:b7,guide:b8,back:b6,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,lefttrigger:a2,righttrigger:a5, 172 | 05000000a00500003232000001000000,8Bitdo Zero GamePad,platform:Linux,a:b0,b:b1,x:b3,y:b4,back:b10,start:b11,leftshoulder:b6,rightshoulder:b7,leftx:a0,lefty:a1, 173 | 030000001008000001e5000010010000,NEXT Classic USB Game Controller,a:b0,b:b1,back:b8,start:b9,rightx:a2,righty:a3,leftx:a0,lefty:a1,platform:Linux, 174 | 03000000100800000300000010010000,USB Gamepad,platform:Linux,a:b2,b:b1,x:b3,y:b0,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b4,righttrigger:b5, 175 | 05000000ac0500003232000001000000,VR-BOX,platform:Linux,a:b0,b:b1,x:b2,y:b3,start:b9,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b6,rightshoulder:b7,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a2,lefttrigger:b4,righttrigger:b5, 176 | 03000000780000000600000010010000,Microntek USB Joystick,platform:Linux,x:b3,a:b2,b:b1,y:b0,back:b8,start:b9,leftshoulder:b6,lefttrigger:b4,rightshoulder:b7,righttrigger:b5,leftx:a0,lefty:a1, 177 | -------------------------------------------------------------------------------- /assets/gfx/hero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bncastle/love2d-tutorial/b2294e45bb6580e3bb327ce4429a53ffcde37840/assets/gfx/hero.png -------------------------------------------------------------------------------- /assets/gfx/missile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bncastle/love2d-tutorial/b2294e45bb6580e3bb327ce4429a53ffcde37840/assets/gfx/missile.png -------------------------------------------------------------------------------- /assets/sfx/hit01.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bncastle/love2d-tutorial/b2294e45bb6580e3bb327ce4429a53ffcde37840/assets/sfx/hit01.wav -------------------------------------------------------------------------------- /conf.lua: -------------------------------------------------------------------------------- 1 | 2 | function love.conf(t) 3 | t.window.width = 640 4 | t.window.height = 360 5 | t.window.display = 2 6 | t.window.x = 150 7 | t.window.y = 690 8 | end -------------------------------------------------------------------------------- /lib/Animation.lua: -------------------------------------------------------------------------------- 1 | local Class = require("lib.Class") 2 | local Vector2 = require("lib.Vector2") 3 | 4 | local Anim = Class:derive("Animation") 5 | 6 | function Anim:new(xoffset, yoffset, w, h, frames, column_size, fps, loop) 7 | self.fps = fps 8 | if type(frames) == "table" then 9 | self.frames = frames 10 | else 11 | self.frames = {} 12 | for i = 1, frames do 13 | self.frames[i] = i 14 | end 15 | end 16 | self.column_size = column_size 17 | self.start_offset = Vector2(xoffset, yoffset) 18 | self.offset = Vector2() 19 | self.size = Vector2(w, h) 20 | --loop = false, playthrough once, otherwise, loop forever 21 | self.loop = loop == nil or loop 22 | self:reset() 23 | end 24 | 25 | function Anim:reset() 26 | self.timer = 1 / self.fps 27 | self.index = 1 28 | self.done = false 29 | 30 | self.offset.x = self.start_offset.x + (self.size.x * ((self.frames[self.index] - 1) % (self.column_size))) 31 | self.offset.y = self.start_offset.y + (self.size.y * math.floor((self.frames[self.index] - 1) / self.column_size)) 32 | end 33 | 34 | function Anim:set(quad) 35 | quad:setViewport(self.offset.x, self.offset.y, self.size.x, self.size.y) 36 | end 37 | 38 | function Anim:update(dt, quad) 39 | if #self.frames <= 1 then 40 | self.done = true 41 | return 42 | 43 | elseif self.timer > 0 then 44 | self.timer = self.timer - dt 45 | if self.timer <= 0 then 46 | self.timer = 1 / self.fps 47 | self.index = self.index + 1 48 | if self.index > #self.frames then 49 | if self.loop then 50 | self.index = 1 51 | else 52 | self.index = #self.frames 53 | self.timer = 0 54 | self.done = true 55 | end 56 | end 57 | self.offset.x = self.start_offset.x + (self.size.x * ((self.frames[self.index] - 1) % (self.column_size))) 58 | self.offset.y = self.start_offset.y + (self.size.y * math.floor((self.frames[self.index] - 1) / self.column_size)) 59 | -- print( self.index .. " " .. self.offset.x .. " " .. self.offset.y) 60 | self:set(quad) 61 | end 62 | end 63 | end 64 | 65 | return Anim -------------------------------------------------------------------------------- /lib/Class.lua: -------------------------------------------------------------------------------- 1 | --[[ 2 | This module gives us some class-like functionality 3 | ]] 4 | 5 | local Class = {} 6 | Class.__index = Class 7 | 8 | --default implementation 9 | function Class:new() end 10 | 11 | --create a new Class type from our base class 12 | function Class:derive(class_type) 13 | assert(class_type ~= nil, "parameter class_type must not be nil!") 14 | assert(type(class_type) == "string", "parameter class_type class must be string!") 15 | local cls = {} 16 | cls["__call"] = Class.__call 17 | cls.type = class_type 18 | cls.__index = cls 19 | cls.super = self 20 | setmetatable(cls, self) 21 | return cls 22 | end 23 | 24 | --Check if the instance is a sub-class of the given type 25 | function Class:is(class) 26 | assert(class ~= nil, "parameter class must not be nil!") 27 | assert(type(class) == "table", "parameter class must be of Type Class!") 28 | local mt = getmetatable(self) 29 | while mt do 30 | if mt == class then return true end 31 | mt = getmetatable(mt) 32 | end 33 | return false 34 | end 35 | 36 | function Class:is_type(class_type) 37 | assert(class_type ~= nil, "parameter class_type must not be nil!") 38 | assert(type(class_type) == "string", "parameter class_type class must be string!") 39 | local base = self 40 | while base do 41 | if base.type == class_type then return true end 42 | base = base.super 43 | end 44 | return false 45 | end 46 | 47 | function Class:__call(...) 48 | local inst = setmetatable({}, self) 49 | inst:new(...) 50 | return inst 51 | end 52 | 53 | function Class:get_type() 54 | return self.type 55 | end 56 | 57 | return Class -------------------------------------------------------------------------------- /lib/Entity.lua: -------------------------------------------------------------------------------- 1 | local Class = require("lib.Class") 2 | local U = require("lib.Utils") 3 | 4 | local E = Class:derive("Entity") 5 | 6 | function E:new(...) 7 | self.components = {} 8 | local components_to_add = {...} 9 | 10 | if #components_to_add > 0 then 11 | for i = 1, #components_to_add do 12 | self:add(components_to_add[i]) 13 | end 14 | end 15 | end 16 | 17 | --helps us sort our priorities because priorities are important 18 | local function priority_compare(e1, e2) 19 | return e1.priority < e2.priority 20 | end 21 | 22 | --Add a component to this entity 23 | -- 24 | --Note: name is optional and if it is not used, the component's class type 25 | --will be used instead 26 | function E:add(component, name) 27 | if U.contains(self.components, component) then return end 28 | --Add additional table entries that we want to exist for all components 29 | 30 | --higher priority components will be updated/drawn before lower priority ones 31 | component.priority = component.priority or 1 32 | --indicates if the on_started function has been called on a component 33 | component.started = component.started or false 34 | --if a component is enabled, then it is drawn/updated 35 | component.enabled = (component.enabled == nil) or component.enabled 36 | --Let the component know who its parent is 37 | component.entity = self 38 | 39 | --Add the component to the list 40 | self.components[#self.components + 1] = component 41 | 42 | if name ~=nil and type(name) == "string" and name.len() > 0 then 43 | assert(self[name] == nil, "This entity already contains a component of name: " .. name) 44 | self[name] = component 45 | elseif component.type and type(component.type) == "string" then 46 | assert(self[component.type] == nil, "This entity already contains a component of name: " .. component.type) 47 | self[component.type] = component 48 | end 49 | 50 | -- if component.on_added then component:on_added() end 51 | 52 | if self.started and not component.started and component.enabled then 53 | component.started = true 54 | if component.on_start then component:on_start() end 55 | end 56 | 57 | --Sort components 58 | table.sort(self.components, priority_compare) 59 | end 60 | 61 | function E:remove(component) 62 | local i = U.index_of(self.components, component) 63 | if i == -1 then return end 64 | 65 | --if the component has an on_remove event, call it 66 | if component.on_remove then component:on_remove() end 67 | --then remove it 68 | table.remove(self.components, i) 69 | 70 | --check if the component has a type property of type string 71 | --if so, remove it from this entity 72 | if component.type and type(component.type) == "string" then 73 | self[component.type] = nil 74 | component.entity = nil 75 | end 76 | end 77 | 78 | function E:on_start() 79 | for i = 1, #self.components do 80 | local component = self.components[i] 81 | --if the component is enabled, then call on_start() if it has one 82 | if component.enabled then 83 | if not component.started then 84 | component.started = true 85 | if component.on_start then component:on_start() end 86 | end 87 | end 88 | end 89 | end 90 | 91 | function E:update(dt) 92 | for i = 1, #self.components do 93 | --if the component is enabled, then update it 94 | if self.components[i].enabled and self.components[i].update then 95 | self.components[i]:update(dt) 96 | end 97 | end 98 | end 99 | 100 | function E:draw() 101 | for i = 1, #self.components do 102 | if self.components[i].enabled and self.components[i].draw then 103 | self.components[i]:draw() 104 | end 105 | end 106 | end 107 | 108 | return E -------------------------------------------------------------------------------- /lib/EntityMgr.lua: -------------------------------------------------------------------------------- 1 | local Class = require("lib.Class") 2 | local U = require("lib.Utils") 3 | 4 | local EM = Class:derive("EntityMgr") 5 | 6 | 7 | local function layer_compare(e1, e2) 8 | return e1.layer < e2.layer 9 | end 10 | 11 | function EM:new() 12 | self.entities = {} 13 | end 14 | 15 | function EM:add(entity) 16 | if U.contains(self.entities, entity) then return end 17 | --Add additional table entries that we want to exist for all entities 18 | entity.layer = entity.layer or 1 19 | entity.started = entity.started or false 20 | entity.updated = entity.updated or false 21 | entity.enabled = (entity.enabled == nil) or entity.enabled 22 | self.entities[#self.entities + 1] = entity 23 | 24 | --Sort entities 25 | table.sort(self.entities, layer_compare) 26 | end 27 | 28 | function EM:on_enter() 29 | for i = 1, #self.entities do 30 | local e = self.entities[i] 31 | if e.on_enter then e:on_enter() end 32 | end 33 | end 34 | 35 | function EM:on_exit() 36 | for i = 1, #self.entities do 37 | local e = self.entities[i] 38 | if e.on_enter then e:on_exit() end 39 | end 40 | end 41 | 42 | function EM:update(dt) 43 | for i = #self.entities, 1, -1 do 44 | local e = self.entities[i] 45 | 46 | --If the entity requests removal then do it 47 | if e.remove == true then 48 | e.remove = false 49 | if e.on_remove then e:on_remove() end 50 | table.remove(self.entities, i) 51 | end 52 | 53 | if e.enabled then 54 | if not e.started then 55 | if e.on_start then e:on_start() end 56 | e.started = true 57 | else--if e.update then 58 | e:update(dt) 59 | e.updated = true 60 | end 61 | end 62 | end 63 | end 64 | 65 | function EM:draw() 66 | for i = 1, #self.entities do 67 | if self.entities[i].enabled and self.entities[i].draw and self.entities[i].updated then 68 | self.entities[i]:draw() 69 | end 70 | end 71 | end 72 | 73 | return EM -------------------------------------------------------------------------------- /lib/Events.lua: -------------------------------------------------------------------------------- 1 | local Class = require("lib.Class") 2 | 3 | local Events = Class:derive("Events") 4 | 5 | function Events:new(event_must_exist) 6 | self.handlers = {} 7 | self.event_must_exist = (event_must_exist == nil) or event_must_exist 8 | end 9 | 10 | local function index_of(evt_tbl, callback) 11 | if(evt_tbl == nil or callback == nil) then return -1 end 12 | for i = 1, #evt_tbl do 13 | if evt_tbl[i] == callback then return i end 14 | end 15 | return -1 16 | end 17 | 18 | --Returns true if the event exists, false otherwise 19 | function Events:exists(evt_type) 20 | return self.handlers[evt_type] ~= nil 21 | end 22 | 23 | --Add a new event type to our table 24 | function Events:add(evt_type) 25 | assert(self.handlers[evt_type] == nil, "Event " .. evt_type .. " already exists!") 26 | self.handlers[evt_type] = {} 27 | end 28 | 29 | --Remove an event type from our table 30 | function Events:remove(evt_type) 31 | self.handlers[evt_type] = nil 32 | end 33 | 34 | --Subscribe to an event 35 | function Events:hook(evt_type, callback) 36 | assert(type(callback) == "function", "callback parameter must be a function!") 37 | 38 | if self.event_must_exist then 39 | assert(self.handlers[evt_type] ~= nil, "Event of type " .. evt_type .. " does not exist!") 40 | elseif(self.handlers[evt_type] == nil) then 41 | self:add(evt_type) 42 | end 43 | 44 | -- if(index_of(self.handlers[evt_type], callback) > -1) then return end 45 | assert(index_of(self.handlers[evt_type], callback) == -1, "callback has already been registered!") 46 | 47 | local tbl = self.handlers[evt_type] 48 | tbl[#tbl + 1] = callback 49 | end 50 | 51 | function Events:unhook(evt_type, callback) 52 | assert(type(callback) == "function", "callback parameter must be a function!") 53 | if self.handlers[evt_type] == nil then return end 54 | local index = index_of(self.handlers[evt_type], callback) 55 | if index > -1 then 56 | table.remove(self.handlers[evt_type], index) 57 | end 58 | end 59 | 60 | --Clears out the event handlers for the givent envent type 61 | function Events:clear(evt_type) 62 | if evt_type == nil then 63 | for k,v in pairs(self.handlers) do 64 | self.handlers[k] = {} 65 | end 66 | elseif self.handlers[evt_type] ~= nil then 67 | self.handlers[evt_type] = {} 68 | end 69 | end 70 | 71 | function Events:invoke(evt_type, ...) 72 | if self.handlers[evt_type] == nil then return end 73 | -- assert(self.handlers[evt_type] ~= nil, "Event of type " .. evt_type .. " does not exist!") 74 | local tbl = self.handlers[evt_type] 75 | for i = 1, #tbl do 76 | tbl[i](...) 77 | end 78 | end 79 | 80 | return Events -------------------------------------------------------------------------------- /lib/GamepadMgr.lua: -------------------------------------------------------------------------------- 1 | local Class = require("lib.Class") 2 | local Events = require("lib.Events") 3 | 4 | local GPM = Class:derive("GamepadMgr") 5 | 6 | local DEAD_ZONE = 0.1 7 | 8 | local function hook_love_events(self) 9 | 10 | function love.joystickadded(joystick) 11 | local id = joystick:getID() 12 | assert(self.connected_sticks[id] == nil, "Joystick " .. id .. "already exists!") 13 | self.connected_sticks[id] = joystick 14 | self.is_connected[id] = true 15 | self.button_map[id] = {} 16 | self.event:invoke('controller_added', id) 17 | end 18 | 19 | function love.joystickremoved(joystick) 20 | local id = joystick:getID() 21 | self.is_connected[id] = false 22 | self.connected_sticks[id] = nil 23 | self.button_map[id] = {} 24 | self.event:invoke('controller_removed', id) 25 | end 26 | 27 | function love.gamepadpressed(joystick, button) 28 | local id = joystick:getID() 29 | self.button_map[id][button] = true 30 | end 31 | 32 | function love.gamepadreleased(joystick, button) 33 | local id = joystick:getID() 34 | self.button_map[id][button] = false 35 | end 36 | end 37 | 38 | function GPM:new(db_files, ad_enabled) 39 | if db_files ~= nil then 40 | for i = 1, #db_files do 41 | love.joystick.loadGamepadMappings(db_files[i]) 42 | end 43 | end 44 | 45 | self.event = Events() 46 | self.event:add('controller_added') 47 | self.event:add('controller_removed') 48 | 49 | hook_love_events(self) 50 | 51 | --if true, then the left analog joystick will be converted to 52 | --its corresponding dpad button output 53 | self.ad_enabled = ad_enabled 54 | 55 | --The currently-connected joysticks 56 | self.connected_sticks = {} 57 | self.is_connected = {} 58 | 59 | --Maps a joystick id to a table of key values 60 | --where the key is a button and the value is either true = just_pressed 61 | --false = just_release, nil = none 62 | self.button_map = {} 63 | end 64 | 65 | --Returns true if a joystick with the given id exists 66 | -- 67 | function GPM:exists(joyId) 68 | return self.is_connected[joyId] == nil and self.is_connected[joyId] 69 | end 70 | 71 | --returns the joystick with the given id 72 | -- 73 | function GPM:get_stick(joyId) 74 | return self.connected_sticks[joyId] 75 | end 76 | 77 | --Returns true if the given button was just pressed THIS frame! 78 | function GPM:button_down(joyId, button) 79 | if self.is_connected[joyId] == nil or self.is_connected[joyId] == false then 80 | return false 81 | else 82 | return self.button_map[joyId][button] == true 83 | end 84 | end 85 | 86 | --Returns true if the given button was just released THIS frame! 87 | function GPM:button_up(joyId, button) 88 | if self.is_connected[joyId] == nil or self.is_connected[joyId] == false then 89 | return false 90 | else 91 | return self.button_map[joyId][button] == false 92 | end 93 | end 94 | 95 | --return the instantaneous state of the requested button for the given joystick 96 | function GPM:button(joyId, button) 97 | local stick = self.connected_sticks[joyid] 98 | if self.is_connected[joyId] == nil or self.is_connected[joyId] == false then return false end 99 | 100 | local is_down = stick:isGamepadDown(button) 101 | 102 | --do we want to convert the left analog stick to dpad buttons? 103 | if self.ad_enabled and not is_down then 104 | local xAxis = stick:getGamepadAxis("leftx") 105 | local yAxis = stick:getGamepadAxis("lefty") 106 | if button == 'dpright' then 107 | is_down = xAxis > DEAD_ZONE 108 | elseif button == 'dpleft' then 109 | is_down = xAxis < -DEAD_ZONE 110 | elseif button == 'dpup' then 111 | is_down = yAxis < -DEAD_ZONE 112 | elseif button == 'dpdown' then 113 | is_down = yAxis > DEAD_ZONE 114 | end 115 | end 116 | return is_down 117 | end 118 | 119 | function GPM:update(dt) 120 | for i = 1, #self.is_connected do 121 | if self.button_map[i] then 122 | for k,_ in pairs(self.button_map[i]) do 123 | self.button_map[i][k] = nil 124 | end 125 | end 126 | end 127 | end 128 | 129 | return GPM -------------------------------------------------------------------------------- /lib/Keyboard.lua: -------------------------------------------------------------------------------- 1 | -- 2 | --This module keeps track of keys pressed and released 3 | -- 4 | 5 | local Keyboard = {} 6 | --A table containing keys that have been pressed 7 | --for the current frame 8 | local key_states = {} 9 | 10 | function Keyboard:update(dt) 11 | for k,v in pairs(key_states) do 12 | key_states[k] = nil 13 | end 14 | end 15 | 16 | --Returns the current state of the given key 17 | function Keyboard:key(key) 18 | return love.keyboard.isDown(key) 19 | end 20 | 21 | --Returns if the key has been pressed THIS frame 22 | function Keyboard:key_down(key) 23 | return key_states[key] 24 | end 25 | 26 | --Returns if the key has been unpressed THIS frame 27 | function Keyboard:key_up(key) 28 | return key_states[key] == false 29 | end 30 | 31 | function Keyboard:hook_love_events() 32 | function love.keypressed(key, scancode, isrepeat) 33 | key_states[key] = true 34 | _G.events:invoke("key_pressed", key) 35 | end 36 | function love.keyreleased(key, scancode) 37 | key_states[key] = false 38 | _G.events:invoke("key_released", key) 39 | end 40 | 41 | function love.textinput(text) 42 | _G.events:invoke("text_input", text) 43 | end 44 | end 45 | 46 | return Keyboard -------------------------------------------------------------------------------- /lib/Rect.lua: -------------------------------------------------------------------------------- 1 | --For a good article on Minowski sums/differences to determin separation vector, see: 2 | --http://hamaluik.com/posts/simple-aabb-collision-using-minkowski-difference/ 3 | 4 | local Class = require("lib.Class") 5 | local Vector2 = require("lib.Vector2") 6 | 7 | local R = Class:derive("Rect") 8 | 9 | local abs = math.abs 10 | 11 | --where (x,y) indicate the upper left corner of the rect 12 | function R:new(x, y, w, h) 13 | self.x = x or 0 14 | self.y = y or 0 15 | self.w = w or 0 16 | self.h = h or 0 17 | end 18 | 19 | --where (x,y) indicates the center of the rect 20 | function R.create_centered(cx, cy, w, h) 21 | local r = R(0,0,w,h) 22 | r:set_center(cx,cy) 23 | return r 24 | end 25 | 26 | function R:center() 27 | return Vector2(self.x + self.w / 2, self.y + self.h / 2) 28 | end 29 | 30 | function R:set_center(x,y) 31 | self.x = x - self.w / 2 32 | self.y = y - self.h / 2 33 | end 34 | 35 | function R:min() 36 | return Vector2(self.x, self.y) 37 | end 38 | 39 | function R:max() 40 | return Vector2(self.x + self.w, self.y + self.h) 41 | end 42 | 43 | function R:size() 44 | return Vector2(self.w, self.h) 45 | end 46 | 47 | --other is also a Rect 48 | function R:minkowski_diff(other) 49 | local top_left = Vector2.sub(self:min(), other:max()) 50 | local newSize = Vector2.add(self:size(), other:size()) 51 | local newLeft = Vector2.add(top_left, Vector2.divide(newSize,2)) 52 | return R.create_centered(newLeft.x, newLeft.y, newSize.x, newSize.y) 53 | end 54 | 55 | --Returns a Vector2 that indicates the point on the 56 | --rectangle's boundary that is closest to the given point 57 | -- 58 | function R:closest_point_on_bounds(point) 59 | local min_dist = abs(point.x - self.x) 60 | local max = self:max() 61 | local bounds_point = Vector2(self.x, point.y) 62 | 63 | --finish checking x axis 64 | if abs(max.x - point.x) < min_dist then 65 | min_dist = abs(max.x - point.x) 66 | bounds_point = Vector2(max.x, point.y) 67 | end 68 | 69 | --move to y axis 70 | if abs(max.y - point.y) < min_dist then 71 | min_dist = abs(max.y - point.y) 72 | bounds_point = Vector2(point.x, max.y) 73 | end 74 | 75 | if abs(self.y - point.y) < min_dist then 76 | min_dist = abs(self.y - point.y) 77 | bounds_point = Vector2(point.x, self.y) 78 | end 79 | 80 | return bounds_point 81 | end 82 | 83 | --Given a bounds point obtained from Rect.closest_point_on_bounds() 84 | --this returns true if a collision occurred on the right side of the rectangle 85 | function R:collides_right(bounds_point) 86 | return bounds_point.x < 0 87 | end 88 | 89 | --Given a bounds point obtained from Rect.closest_point_on_bounds() 90 | --this returns true if a collision occurred on the left side of the rectangle 91 | function R:collides_left(bounds_point) 92 | return bounds_point.x > 0 93 | end 94 | 95 | --Given a bounds point obtained from Rect.closest_point_on_bounds() 96 | --this returns true if a collision occurred on the top of the rectangle 97 | function R:collides_bottom(bounds_point) 98 | return bounds_point.y < 0 99 | end 100 | 101 | --Given a bounds point obtained from Rect.closest_point_on_bounds() 102 | --this returns true if a collision occurred on the bottom of the rectangle 103 | function R:collides_top(bounds_point) 104 | return bounds_point.y > 0 105 | end 106 | return R -------------------------------------------------------------------------------- /lib/Sat.lua: -------------------------------------------------------------------------------- 1 | local Vector2 = require("lib.Vector2") 2 | 3 | local S = {} 4 | 5 | local function contains_axis(edges, unit_normal) 6 | for i = 1, #edges do 7 | if math.abs(unit_normal:dot(edges[i])) == 1 then return true end 8 | end 9 | return false 10 | end 11 | 12 | --Retrieves all the edge normals from a polygon 13 | -- given a list of Vector2 points that make up the polygon 14 | -- 15 | local function get_edge_normals(vertices) 16 | if vertices == nil or #vertices == 0 then return nil end 17 | 18 | local edge_normals = {} 19 | 20 | for i = 1, #vertices do 21 | local p1 = vertices[i] 22 | local p2 = vertices[i + 1] or vertices[1] 23 | --subtract these 2 to get the edge vector 24 | local edge = Vector2.sub(p1,p2) 25 | --Get the edge normal 26 | local normal = edge:normal():unit() 27 | 28 | --Check if the edge is valid (i.e. magnitude is > 0) 29 | if edge:mag() > 0 and not contains_axis(edge_normals, normal) then 30 | edge_normals[#edge_normals + 1] = normal 31 | end 32 | end 33 | 34 | return edge_normals 35 | end 36 | 37 | --Projects the given vertices onto the given axis 38 | -- 39 | local function project_onto_axis(vertices, axis) 40 | local min = axis:dot(vertices[1]) 41 | local max = min 42 | 43 | for i = 2, #vertices do 44 | local proj = axis:dot(vertices[i]) 45 | if proj < min then min = proj 46 | elseif proj > max then max = proj 47 | end 48 | end 49 | return min, max 50 | end 51 | 52 | function S.to_Vector2_array(tbl) 53 | local vertices = {} 54 | 55 | assert(#tbl % 2 == 0, "Table must have an even number of elements!") 56 | 57 | for i = 1, #tbl, 2 do 58 | vertices[#vertices + 1] = Vector2(tbl[i], tbl[i + 1]) 59 | end 60 | 61 | return vertices 62 | end 63 | 64 | --Performs the Separating Axis Theorem calculations on the 65 | --given polygons. returns the minimum penetration axis and overlap amount separation vector if colliding 66 | --nil,nil otherwise 67 | -- 68 | function S.Collide(poly1, poly2) 69 | local axes = get_edge_normals(poly1) 70 | local axes2 = get_edge_normals(poly2) 71 | local min_pentration_axis = nil 72 | local overlap = 0xEFFFFFFF 73 | --concatenate axes2 into axes 74 | for i =1, #axes2 do axes[#axes + 1] = axes2[i] end 75 | 76 | --loop over all the possible separating axes and 77 | --if we find even 1 that does not overlap, then we are DONE 78 | for i = 1, #axes do 79 | local axis = axes[i] 80 | --project both shapes onto the current axis 81 | local p1min, p1max = project_onto_axis(poly1, axis) 82 | local p2min, p2max = project_onto_axis(poly2, axis) 83 | 84 | --now check for no overlap. If no overlap, return nil and we're done 85 | if p1min > p2max or p2min > p1max then return nil end 86 | --otherwise, grab the amount of overlap 87 | local o = math.min(p1max, p2max) - math.max(p1min, p2min) 88 | if o < overlap then 89 | overlap = o 90 | min_pentration_axis = axis 91 | end 92 | end 93 | 94 | --if we get here then we are colliding 95 | assert(min_pentration_axis ~=nil) 96 | return min_pentration_axis, overlap 97 | end 98 | 99 | return S -------------------------------------------------------------------------------- /lib/Scene.lua: -------------------------------------------------------------------------------- 1 | local Class = require("lib.Class") 2 | local EntityMgr = require("lib.EntityMgr") 3 | 4 | local Scene = Class:derive("Scene") 5 | 6 | --function gets called when the scene is loaded 7 | function Scene:new(scene_mgr) 8 | self.scene_mgr = scene_mgr 9 | self.em = EntityMgr() 10 | end 11 | 12 | --gets called each time the scene is switched or entered as the main scene 13 | function Scene:enter() self.em:on_enter() end 14 | --gets called each time the scene is exited and no longer the main scene 15 | function Scene:exit() self.em:on_exit() end 16 | function Scene:update(dt) self.em:update(dt) end 17 | function Scene:draw() self.em:draw() end 18 | --this is called when the scene is removed from the scene manager 19 | function Scene:destroy() end 20 | 21 | return Scene -------------------------------------------------------------------------------- /lib/SceneMgr.lua: -------------------------------------------------------------------------------- 1 | local Class = require("lib.Class") 2 | local Scene = require("lib.Scene") 3 | 4 | local SM = Class:derive("SceneMgr") 5 | 6 | function SM:new(scene_dir, scenes) 7 | self.scenes = {} 8 | self.scene_dir = scene_dir 9 | 10 | if not scene_dir then scene_dir = "" end 11 | 12 | if scenes ~= nil then 13 | assert(type(scenes) == "table", "parameter scenes must be table!") 14 | for i =1, #scenes do 15 | local M = require(scene_dir .. "." .. scenes[i]) 16 | assert(M:is(Scene), "File: " .. scene_dir .. "." .. scenes[i] .. ".lua is not of type Scene!") 17 | self.scenes[scenes[i]] = M(self) 18 | end 19 | end 20 | 21 | --these are strings that are keys into the self.scenes table 22 | self.prev_scene_name = nil 23 | self.current_scene_name = nil 24 | --this contains the actual scene object 25 | self.current = nil 26 | end 27 | 28 | --adds a scene to the list where scene is an instance of a sub-class of Scene 29 | function SM:add(scene, scene_name) 30 | if scene then 31 | assert(scene_name ~= nil, "parameter scene_name must be specified!") 32 | assert(type(scene_name) == "string", "parameter scene_name must be string!") 33 | assert(type(scene) == "table", "parameter scene must be table!") 34 | assert(scene:is(Scene), "cannot add non-scene object to the scene manager!") 35 | --assuming the scene is already contructed 36 | self.scenes[scene_name] = scene 37 | 38 | end 39 | end 40 | 41 | --removes a scene from our list 42 | function SM:remove(scene_name) 43 | if scene_name then 44 | for k,v in pairs(self.scenes) do 45 | if k == scene_name then 46 | self.scenes[k]:destroy() 47 | self.scenes[k] = nil 48 | if scene_name == self.current_scene_name then 49 | self.current = nil 50 | end 51 | break 52 | end 53 | end 54 | end 55 | end 56 | 57 | --switches from the current scene to the next_scene 58 | function SM:switch(next_scene) 59 | if self.current then 60 | self.current:exit() 61 | end 62 | 63 | if next_scene then 64 | assert(self.scenes[next_scene] ~= nil, "Unable to find scene:" .. next_scene) 65 | self.prev_scene_name = self.current_scene_name 66 | self.current_scene_name = next_scene 67 | self.current = self.scenes[next_scene] 68 | self.current:enter() 69 | end 70 | end 71 | 72 | --Returns to the previous scene if there is one 73 | function SM:pop() 74 | if self.prev_scene_name then 75 | self:switch(self.prev_scene_name) 76 | self.prev_scene_name = nil 77 | end 78 | end 79 | 80 | --Gives a list of all the scene names that the scene manager knows about 81 | function SM:get_available_scenes() 82 | local scene_names = {} 83 | for k,v in pairs(self.scenes) do 84 | scene_names[#scene_names + 1] = k 85 | end 86 | return scene_names 87 | end 88 | 89 | function SM:update(dt) 90 | if self.current then 91 | self.current:update(dt) 92 | end 93 | end 94 | function SM:draw() 95 | if self.current then 96 | self.current:draw(dt) 97 | end 98 | end 99 | 100 | return SM -------------------------------------------------------------------------------- /lib/Tween.lua: -------------------------------------------------------------------------------- 1 | 2 | local pow = math.pow 3 | local sin = math.sin 4 | local cos = math.cos 5 | local PI = math.pi 6 | 7 | local T = {} 8 | local active_tweens = {} 9 | 10 | --Easing functions 11 | function T.linear(ratio) return ratio end 12 | 13 | function T.quad_in(ratio) return pow(ratio, 2) end 14 | function T.quad_out(ratio) return ratio * (2 - ratio) end 15 | function T.quad_inout(ratio) 16 | if ratio < 0.5 then 17 | return 2 * pow(ratio,2) 18 | else 19 | return 1 - 2 * pow(ratio - 1, 2) 20 | end 21 | end 22 | 23 | function T.cubic_in(ratio) return pow(ratio, 3) end 24 | function T.cubic_out(ratio) return pow(ratio -1, 3) + 1 end 25 | function T.cubic_inout(ratio) 26 | if ratio < 0.5 then 27 | return 4 * pow(ratio,3) 28 | else 29 | return 1 + 4 * pow((ratio -1), 3) 30 | end 31 | end 32 | 33 | 34 | function T.quart_in(ratio) return pow(ratio, 4) end 35 | function T.quart_out(ratio) return 1 - pow(ratio - 1, 4) end 36 | function T.quart_inout(ratio) 37 | if ratio < 0.5 then 38 | return 8 * pow(ratio,4) 39 | else 40 | return 1 - 8 * pow(ratio - 1, 4) 41 | end 42 | end 43 | 44 | function T.quint_in(ratio) return pow(ratio, 5) end 45 | function T.quint_out(ratio) return pow(ratio -1, 5) + 1 end 46 | function T.quint_inout(ratio) 47 | if ratio < 0.5 then 48 | return 16 * pow(ratio,5) 49 | else 50 | return 1 + 16 * pow(ratio -1, 5) 51 | end 52 | end 53 | 54 | function T.sine_in(ratio) return 1 - cos(ratio * PI / 2) end 55 | function T.sine_out(ratio) return sin(ratio * PI / 2) end 56 | function T.sine_inout(ratio) return (1 + sin(ratio * PI - PI /2)) / 2 end 57 | 58 | function T.back_out(ratio) return ratio * (2 - ratio * ratio) end 59 | --TODO Create elastic and bounce easings 60 | 61 | -- function T:create(target, prop_name, from, to, duration) 62 | function T.create(target, prop_name, to, duration, ease_function) 63 | assert(type(target) == "table", "target parameter must be a table!") 64 | assert(type(prop_name) == "string", "prop_name parameter must be a string!") 65 | 66 | -- 67 | local tween = { 68 | t = 0, 69 | from = target[prop_name], 70 | diff = to - target[prop_name], 71 | to = to, 72 | duration = duration, 73 | target = target, 74 | update = function(self, dt) 75 | if self.stop_tween then return false end 76 | if self.t >= self.duration then 77 | self.target[prop_name] = to 78 | return false 79 | end 80 | 81 | --tween the property here 82 | self.target[prop_name] = self.from + self.diff * ease_function(self.t / self.duration) 83 | 84 | self.t = self.t + dt 85 | return true 86 | end, 87 | cancel = function(self, should_complete) 88 | self.stop_tween = true 89 | if should_complete then 90 | self.target[prop_name] = self.to 91 | end 92 | end 93 | } 94 | 95 | --add tween to active 96 | active_tweens[#active_tweens + 1] = tween 97 | --return the new tween table 98 | return tween 99 | end 100 | 101 | function T.update(dt) 102 | for i=#active_tweens, 1, -1 do 103 | if not active_tweens[i]:update(dt) then 104 | local t = active_tweens[i] 105 | table.remove(active_tweens, i) 106 | if(t.on_complete) then t:on_complete() end 107 | end 108 | end 109 | end 110 | 111 | return T -------------------------------------------------------------------------------- /lib/Utils.lua: -------------------------------------------------------------------------------- 1 | local Vector2 = require("lib.Vector2") 2 | 3 | local U = {} 4 | local pow = math.pow 5 | local sqrt = math.sqrt 6 | 7 | function U.color(r, g, b, a) 8 | return {r, g or r, b or r, a or 255} 9 | end 10 | 11 | function U.gray(level, alpha) 12 | return {level, level, level, alpha or 255} 13 | end 14 | 15 | function U.point_in_rect(point, rect) 16 | return not( 17 | point.x < rect.x or 18 | point.x > rect.x + rect.w or 19 | point.y < rect.y or 20 | point.y > rect.y + rect.h) 21 | end 22 | 23 | --rx,ry is the upper left corner of the rectangle 24 | --rw,rh is the width and height of the rectangle 25 | function U.mouse_in_rect(mx, my, rx, ry, rw, rh) 26 | return not (mx < rx or mx > rx + rw or my < ry or my > ry + rh) 27 | end 28 | 29 | --Axis-Aligned Bounding Box collision detection 30 | -- 31 | function U.AABBColl(rect1, rect2) 32 | local rect1r = rect1.x + rect1.w 33 | local rect1b = rect1.y + rect1.h 34 | 35 | local rect2r = rect2.x + rect2.w 36 | local rect2b = rect2.y + rect2.h 37 | 38 | return rect1r >= rect2.x and rect2r >= rect1.x and 39 | rect1b >= rect2.y and rect2b >= rect1.y 40 | end 41 | 42 | --Circle to circle collision. Refine this! 43 | --returns boolean -> true if circles overlap, false otherwise, 44 | --if colliding then also returns a minimum separation vector 45 | function U.CircleOverlaps(circle1, circle2) 46 | local c1toc2 = Vector2(circle1.x - circle2.x, circle1.y - circle2.y) 47 | local d = c1toc2:mag() 48 | 49 | local overlaps = d < circle1.r + circle2.r 50 | 51 | if overlaps then 52 | c1toc2:unit() 53 | c1toc2:mul(circle1.r + circle2.r - d) 54 | return true, c1toc2 55 | else 56 | return false 57 | end 58 | end 59 | 60 | --Collides circle1 with circle2 if they overlap 61 | --it will apply a separation to one or both circles 62 | --parameters: circle1, circle2 are the circles in question 63 | -- ratio: a number from 0 to 1 that affects how much of the separation 64 | -- to apply to circle1 vs circle2 65 | -- 66 | --returns: true if the two circles collided, false otherwise 67 | function U.CirclesCollide(circle1, circle2, ratio) 68 | ratio = ratio or 1 69 | 70 | --ensure that the ratio is between 0 and 1 71 | if ratio < 0 then ratio = 0 72 | elseif ratio > 1 then ratio = 1 73 | end 74 | 75 | local overlaps, sep = U.CircleOverlaps(circle1, circle2) 76 | if overlaps then 77 | --With this, only the first circle will be moved 78 | circle1.x = circle1.x + sep.x * ratio 79 | circle1.y = circle1.y + sep.y * ratio 80 | 81 | circle2.x = circle2.x - sep.x * (1 - ratio) 82 | circle2.y = circle2.y - sep.y * (1 - ratio) 83 | end 84 | return overlaps 85 | end 86 | 87 | --returns true if the given item is contained 88 | --within the specified table 89 | -- 90 | -- list = the table inwhich to search for the item 91 | -- item = the item for which to search 92 | function U.contains(list, item) 93 | for val in pairs(list) do 94 | if val == item then return true end 95 | end 96 | return false 97 | end 98 | 99 | --returns the index into the list where the item is located 100 | --if item is not found, -1 is returned 101 | -- 102 | function U.index_of(list, item) 103 | for i, val in ipairs(list) do 104 | if val == item then return i end 105 | end 106 | return -1 107 | end 108 | 109 | -- 110 | --Rotates the given point about the origin 111 | --the given angle 112 | --Note: the last 2 parameters are optional and allow you to 113 | --add an offset to the results AFTER they have been rotated 114 | function U.rotate_point(x, y, angle, post_rotate_x_offset, post_rotate_y_offset) 115 | local xrot = math.cos(angle) * x - math.sin(angle) * y + post_rotate_x_offset or 0 116 | local yrot = math.sin(angle) * x + math.cos(angle) * y + post_rotate_y_offset or 0 117 | --return rotated point 118 | return xrot, yrot 119 | end 120 | 121 | --exclusive or function where a and b are booleans 122 | -- 123 | function U.xor(a,b) 124 | return a ~= b 125 | end 126 | 127 | --returns true if a and b have the same sign 128 | -- 129 | function U.same_sign(a,b) 130 | return U.xor(a >= 0, b < 0 ) 131 | end 132 | 133 | return U -------------------------------------------------------------------------------- /lib/Vector2.lua: -------------------------------------------------------------------------------- 1 | local Class = require("lib.Class") 2 | 3 | local V = Class:derive("Vector2") 4 | 5 | local pow = math.pow 6 | local sqrt = math.sqrt 7 | 8 | function V:new(x,y) 9 | self.x = x or 0 10 | self.y = y or 0 11 | end 12 | 13 | --Calculates the magnitude of the vector 14 | function V:mag() 15 | return sqrt(pow(self.x,2) + pow(self.y,2)) 16 | end 17 | 18 | function V.add(v1, v2) 19 | return V(v1.x + v2.x, v1.y + v2.y) 20 | end 21 | 22 | function V.sub(v1, v2) 23 | return V(v1.x - v2.x, v1.y - v2.y) 24 | end 25 | 26 | function V.divide(v1, divisor) 27 | assert(divisor ~= 0, "Error divisor must not be 0!") 28 | return V(v1.x / divisor, v1.y / divisor) 29 | end 30 | 31 | function V.multiply(v1, mult) 32 | return V(v1.x * mult, v1.y * mult) 33 | end 34 | 35 | --Returns the dot product of the vector with the specified one 36 | --note: this number is a scalar 37 | -- 38 | function V:dot(other) 39 | return self.x * other.x + self.y * other.y 40 | end 41 | 42 | function V:mul(val) 43 | self.x = self.x * val 44 | self.y = self.y * val 45 | return self 46 | end 47 | 48 | function V:div(val) 49 | assert(val ~= 0, "Error val must not be 0!") 50 | self.x = self.x / val 51 | self.y = self.y / val 52 | return self 53 | end 54 | 55 | --Modifies the vector in-place to have a magnitude of 1 56 | -- this is commonly referred to as a unit vector 57 | function V:unit() 58 | local mag = self:mag() 59 | self.x = self.x / mag 60 | self.y = self.y / mag 61 | return self 62 | end 63 | 64 | --Returns a vector that is the normal of this one (perpendicular) 65 | -- 66 | function V:normal() 67 | return V(self.y, -self.x) 68 | end 69 | 70 | -- 71 | --Rotates the Vector2 about the origin the given angle 72 | --Note: the last 2 parameters are optional and allow you to 73 | --add an offset to the results AFTER they have been rotated 74 | --Note: Modifies the object in-place 75 | function V:rotate(angle, xoffset, yoffset) 76 | local nx = math.cos(angle) * self.x - math.sin(angle) * self.y + (xoffset or 0) 77 | local ny = math.sin(angle) * self.x + math.cos(angle) * self.y + (yoffset or 0) 78 | self.x = nx 79 | self.y = ny 80 | end 81 | 82 | --Creates a copy of this Vector2 object 83 | -- 84 | function V:copy() 85 | return V(self.x, self.y) 86 | end 87 | 88 | return V -------------------------------------------------------------------------------- /lib/Vector3.lua: -------------------------------------------------------------------------------- 1 | local Class = require("lib.Class") 2 | 3 | local V = Class:derive("Vector3") 4 | 5 | function V:new(x,y, z) 6 | self.x = x or 0 7 | self.y = y or 0 8 | self.z = z or 0 9 | end 10 | 11 | --Takes 2 vector3 objects and returns their cross product 12 | -- 13 | function V.cross(a, b) 14 | --NOTE: if both vectors are 2D, then this isnt really a cross product!It is something else. 15 | if a.z == nil and b.z == nil then 16 | return V(0, 0, a.x*b.y - a.y*b.x) 17 | else 18 | return V(a.y*b.z - a.z*b.y, a.z*b.x - a.x*b.z, a.x*b.y - a.y*b.x) 19 | end 20 | end 21 | 22 | return V -------------------------------------------------------------------------------- /lib/components/Sprite.lua: -------------------------------------------------------------------------------- 1 | local Class = require("lib.Class") 2 | local Vector2 = require("lib.Vector2") 3 | local Anim = require("lib.Animation") 4 | local Rect = require("lib.Rect") 5 | local U = require("lib.Utils") 6 | 7 | local Sprite = Class:derive("Sprite") 8 | 9 | --where x,y is the center of the sprite 10 | -- 11 | --Note: This component assumes the presence of a Transform component! 12 | -- 13 | function Sprite:new(atlas, w, h, color) 14 | self.w = w 15 | self.h = h 16 | self.flip = Vector2(1,1) 17 | self.atlas = atlas 18 | self.animations = {} 19 | self.current_anim = "" 20 | self.quad = love.graphics.newQuad(0,0, w, h, atlas:getDimensions()) 21 | self.tintColor = color or {1,1,1,1} 22 | end 23 | 24 | function Sprite:on_start() 25 | assert(self.entity.Transform ~=nil, "Sprite component requires a Transform component to exist in the attached entity!") 26 | self.tr = self.entity.Transform 27 | end 28 | 29 | function Sprite:animate(anim_name) 30 | if self.current_anim ~= anim_name and self.animations[anim_name] ~= nil then 31 | self.current_anim = anim_name 32 | self.animations[anim_name]:reset() 33 | self.animations[anim_name]:set(self.quad) 34 | elseif self.animations[anim_name] == nil then 35 | assert(false, anim_name .. " animation not found!") 36 | end 37 | end 38 | 39 | function Sprite:flip_h(flip) 40 | if flip then 41 | self.flip.x = -1 42 | else 43 | self.flip.x = 1 44 | end 45 | end 46 | 47 | function Sprite:flip_v(flip) 48 | if flip then 49 | self.flip.y = -1 50 | else 51 | self.flip.y = 1 52 | end 53 | end 54 | 55 | function Sprite:animation_finished() 56 | if self.animations[self.current_anim] ~= nil then 57 | return self.animations[self.current_anim].done 58 | end 59 | return true 60 | end 61 | 62 | function Sprite:add_animations(animations) 63 | assert(type(animations) == "table", "animations parameter must be a table!") 64 | for k,v in pairs(animations) do 65 | self.animations[k] = v 66 | end 67 | end 68 | 69 | function Sprite:update(dt) 70 | if self.animations[self.current_anim] ~= nil then 71 | self.animations[self.current_anim]:update(dt, self.quad) 72 | end 73 | end 74 | 75 | function Sprite:rect() 76 | return Rect.create_centered(self.tr.x , self.tr.y, self.w * self.tr.sx, self.h * self.tr.sy) 77 | end 78 | 79 | function Sprite:poly() 80 | local x = (self.w / 2 * self.tr.sx) 81 | local y = (self.h / 2 * self.tr.sy) 82 | 83 | local rx1,ry1 = U.rotate_point(-x, -y, self.tr.angle, self.tr.x, self.tr.y) 84 | local rx2,ry2 = U.rotate_point( x, -y, self.tr.angle, self.tr.x, self.tr.y) 85 | local rx3,ry3 = U.rotate_point( x, y, self.tr.angle, self.tr.x, self.tr.y) 86 | local rx4,ry4 = U.rotate_point(-x, y, self.tr.angle, self.tr.x, self.tr.y) 87 | local p ={ rx1, ry1, rx2, ry2, rx3, ry3, rx4, ry4 } 88 | return p 89 | end 90 | 91 | function Sprite:draw() 92 | love.graphics.setColor(self.tintColor) 93 | love.graphics.draw(self.atlas, self.quad, self.tr.x, self.tr.y, self.tr.angle, self.tr.sx * self.flip.x, self.tr.sy * self.flip.y, self.w / 2, self.h / 2) 94 | end 95 | 96 | return Sprite -------------------------------------------------------------------------------- /lib/components/StateMachine.lua: -------------------------------------------------------------------------------- 1 | local Class = require("lib.Class") 2 | -- local Vector2 = require("lib.Vector2") 3 | 4 | local SM = Class:derive("StateMachine") 5 | 6 | local transition = { 7 | enter = "_enter", 8 | none = "", 9 | exit = "_exit" 10 | } 11 | 12 | function SM:new(state_table, start_state_name) 13 | assert(type(state_table) == "table", "state_table parameter must be a table!") 14 | assert(type(start_state_name) == "string" or type(start_state_name) == "nil", "start_state_name parameter must be of type string or nil!") 15 | self:reset() 16 | self.state_table = state_table 17 | self:change(start_state_name) 18 | end 19 | 20 | function SM:reset() 21 | self.state = nil 22 | self.state_name = nil 23 | self.prev_name = nil 24 | self.transition = transition.none 25 | end 26 | 27 | -- Takes a state name and a transition type and returns true if 28 | -- it is able to set the state to that state name and transition type 29 | -- Otherwise, it returns false 30 | -- 31 | function SM:set_transition_function(new_state_name, transition_type) 32 | assert(type(new_state_name) == "string" or type(new_state_name) == "nil", "new_state_name parameter must be of type string or nil!") 33 | assert(type(transition_type) == "string", "transition_type parameter must be a string!") 34 | 35 | if new_state_name == nil or self.state_table[new_state_name .. transition_type] == nil then 36 | self.state = nil 37 | self.transition = transition.none 38 | return false 39 | else 40 | self.state = self.state_table[new_state_name .. transition_type] 41 | assert(type(self.state) == "function", new_state_name .. " must be a function. Is this really a state?") 42 | -- print("prev: " .. (self.state_name or "") .. self.transition .. " current: " .. (new_state_name .. transition_type or "")) 43 | self.transition = transition_type 44 | return true 45 | end 46 | 47 | end 48 | 49 | function SM:change(new_state_name, immediate) 50 | --If were already in the same state, just return for now 51 | if new_state_name == self.state then return end 52 | 53 | --See if the previous state has an exit function 54 | if not immediate and self:set_transition_function(self.state_name, transition.exit) then 55 | elseif self:set_transition_function(new_state_name, transition.enter) then 56 | else self:set_transition_function(new_state_name, transition.none) 57 | end 58 | 59 | --Store the previous state name and set the current one 60 | self.prev_name = self.state_name 61 | self.state_name = new_state_name 62 | end 63 | 64 | function SM:update(dt) 65 | if self.transition == transition.exit then 66 | self.state(self.state_table, dt) 67 | if self:set_transition_function(self.state_name, transition.enter) then 68 | else self:set_transition_function(self.state_name, transition.none) 69 | end 70 | elseif self.transition == transition.enter then 71 | self.state(self.state_table, dt) 72 | self:set_transition_function(self.state_name, transition.none) 73 | elseif self.state ~= nil then 74 | self.state(self.state_table, dt) 75 | end 76 | end 77 | 78 | return SM 79 | -------------------------------------------------------------------------------- /lib/components/Transform.lua: -------------------------------------------------------------------------------- 1 | local Class = require("lib.Class") 2 | local Vector2 = require("lib.Vector2") 3 | 4 | local T = Class:derive("Transform") 5 | 6 | function T:new(x, y, sx, sy, angle) 7 | self.x = x or 0 8 | self.y = y or 0 9 | self.sx = sx or 1 10 | self.sy = sy or 1 11 | self.angle = angle or 0 12 | self.enabled = true 13 | self.started = true 14 | end 15 | 16 | function T:VectorPos() return Vector2(self.x, self.y) end 17 | 18 | return T -------------------------------------------------------------------------------- /lib/components/physics/CircleCollider.lua: -------------------------------------------------------------------------------- 1 | local Class = require("lib.Class") 2 | 3 | local CC = Class:derive("CircleCollider") 4 | 5 | function CC:new(radius) 6 | self.r = radius 7 | end 8 | 9 | function CC:on_start() 10 | assert(self.entity.Transform ~=nil, "CircleCollider component requires a Transform component to exist in the attached entity!") 11 | self.tr = self.entity.Transform 12 | end 13 | 14 | -- function CC:update(dt) 15 | -- end 16 | 17 | function CC:draw() 18 | love.graphics.circle("line", self.tr.x, self.tr.y, self.r) 19 | end 20 | 21 | return CC -------------------------------------------------------------------------------- /lib/components/physics/PolygonCollider.lua: -------------------------------------------------------------------------------- 1 | local Class = require("lib.Class") 2 | local Vector2 = require("lib.Vector2") 3 | 4 | local PC = Class:derive("PolygonCollider") 5 | 6 | --expects an array of Vector2D objects 7 | --These vertices should be centered around the origin 8 | function PC:new(vertices) 9 | self.vertices = vertices 10 | --Scaled and translated vertices (we use these for collision detection) 11 | self.world_vertices = {} 12 | self.draw_points = {} 13 | 14 | for i = 1, #self.vertices do 15 | self.world_vertices[#self.world_vertices + 1] = Vector2() 16 | --there are 2 draw points for every vertex (x,y) 17 | self.draw_points[#self.draw_points + 1] = 0 18 | self.draw_points[#self.draw_points + 1] = 0 19 | end 20 | end 21 | 22 | function PC:on_start() 23 | assert(self.entity.Transform ~=nil, "PolygonCollider component requires a Transform component to exist in the attached entity!") 24 | self.tr = self.entity.Transform 25 | self:scale_translate() 26 | end 27 | 28 | function PC:scale_translate() 29 | --update the polygon's rotation/scale 30 | for i = 1, #self.vertices do 31 | self.world_vertices[i].x = self.vertices[i].x * self.tr.sx 32 | self.world_vertices[i].y = self.vertices[i].y * self.tr.sy 33 | self.world_vertices[i]:rotate(self.tr.angle, self.tr.x, self.tr.y) 34 | 35 | self.draw_points[1 + 2*(i - 1)] = self.world_vertices[i].x 36 | self.draw_points[1 + 2*(i - 1) + 1] = self.world_vertices[i].y 37 | end 38 | end 39 | 40 | function PC:update(dt) 41 | self:scale_translate() 42 | end 43 | 44 | function PC:draw() 45 | love.graphics.polygon("line", self.draw_points) 46 | end 47 | 48 | return PC -------------------------------------------------------------------------------- /lib/ui/Bar.lua: -------------------------------------------------------------------------------- 1 | local U = require("lib.Utils") 2 | local Class = require("lib.Class") 3 | local Vector2 = require("lib.Vector2") 4 | 5 | local B = Class:derive("Bar") 6 | 7 | local border_thickness = 2 8 | 9 | function B:new(id, x, y, w, h, text) 10 | self.id = id 11 | self.pos = Vector2(x or 0, y or 0) 12 | self.w = w 13 | self.h = h 14 | self.text = text 15 | self.percentage = 0 16 | self.filled_value = self.w - border_thickness 17 | 18 | --Text colors 19 | self.text_color = U.color(1) 20 | self.fill_color = U.color(1,0,0, 1) 21 | self.outline_color = U.color(1) 22 | end 23 | 24 | --A value between 0 and 100 25 | function B:set(percentage) 26 | local new_percentage = math.max(0, math.min(percentage, 100)) 27 | 28 | if self.percentage ~= new_percentage then 29 | self.percentage = new_percentage 30 | _G.events:invoke("onBarChanged", self, new_percentage) 31 | else 32 | self.percentage = new_percentage 33 | end 34 | -- self.text = tostring(percentage .. "%") 35 | end 36 | 37 | function B:update(dt) end 38 | 39 | function B:draw() 40 | -- love.graphics.line(self.pos.x, self.pos.y - self.h / 2, self.pos.x, self.pos.y + self.h / 2) 41 | -- love.graphics.line(self.pos.x - self.w / 2, self.pos.y, self.pos.x + self.w / 2, self.pos.y) 42 | local r, g, b, a = love.graphics.getColor() 43 | local lw = love.graphics.getLineWidth() 44 | 45 | --The bar inside color 46 | love.graphics.setColor(self.fill_color) 47 | 48 | local fillamount = self.filled_value * self.percentage / 100 49 | if fillamount > 0 then 50 | love.graphics.rectangle("fill", self.pos.x - self.w / 2 + border_thickness / 2, self.pos.y - self.h / 2, fillamount, self.h, 2, 2) 51 | end 52 | 53 | --The bar outline 54 | love.graphics.setLineWidth(border_thickness) 55 | love.graphics.setColor(self.outline_color) 56 | love.graphics.rectangle("line", self.pos.x - self.w / 2, self.pos.y - self.h / 2, self.w, self.h, 2, 2) 57 | love.graphics.setLineWidth(lw) 58 | 59 | local f = love.graphics.getFont() 60 | local _, lines = f:getWrap(self.text, self.w) 61 | local fh = f:getHeight() 62 | 63 | love.graphics.setColor(self.text_color) 64 | love.graphics.printf(self.text, self.pos.x - self.w / 2, self.pos.y - (fh /2 * #lines), self.w, "center") 65 | love.graphics.setColor(r, g, b, a) 66 | end 67 | 68 | return B -------------------------------------------------------------------------------- /lib/ui/Button.lua: -------------------------------------------------------------------------------- 1 | local U = require("lib.Utils") 2 | local Class = require("lib.Class") 3 | local Vector2 = require("lib.Vector2") 4 | 5 | local Button = Class:derive("Button") 6 | 7 | local function mouse_in_bounds(self, mx, my) 8 | return mx >= self.pos.x - self.w / 2 and mx <= self.pos.x + self.w / 2 and my >= self.pos.y - self.h / 2 and my <= self.pos.y + self.h / 2 9 | end 10 | 11 | function Button:new(x, y, w, h, text) 12 | self.pos = Vector2(x or 0, y or 0) 13 | self.w = w 14 | self.h = h 15 | self.text = text 16 | 17 | --Button Colors 18 | self.normal = U.color(0.5, 0.125, 0.125, 0.75) 19 | self.highlight = U.color(0.75, 0.125, 0.125, 1) 20 | self.pressed = U.color(1, 0.125, 0.125, 1) 21 | self.disabled = U.gray(0.5, 0.5) 22 | 23 | --Text colors 24 | self.text_normal = U.color(1) 25 | self.text_disabled = U.gray(0.5, 1) 26 | 27 | 28 | self.text_color = self.text_normal 29 | self.color = self.normal 30 | self.prev_left_click = false 31 | self.interactible = true 32 | end 33 | 34 | function Button:text_colors(normal, disabled) 35 | assert(type(normal) == "table", "normal parameter must be a table!") 36 | assert(type(disabled) == "table", "disabled parameter must be a table!") 37 | 38 | self.text_normal = normal 39 | self.text_disabled = disabled 40 | end 41 | 42 | function Button:colors(normal, highlight, pressed, disabled) 43 | assert(type(normal) == "table", "normal parameter must be a table!") 44 | assert(type(highlight) == "table", "highlight parameter must be a table!") 45 | assert(type(pressed) == "table", "pressed parameter must be a table!") 46 | --assert(type(disabled) == "table", "disabled parameter must be a table!") 47 | self.normal = normal 48 | self.highlight = highlight 49 | self.pressed = pressed 50 | self.disabled = disabled or self.disabled 51 | end 52 | 53 | --Set the left position of the button 54 | -- 55 | function Button:left(x) 56 | self.pos.x = x + self.w / 2 57 | end 58 | 59 | --Set the top position of the button 60 | -- 61 | function Button:top(y) 62 | self.pos.y = y + self.h / 2 63 | end 64 | 65 | function Button:enable(enabled) 66 | self.interactible = enabled 67 | if not enabled then 68 | self.color = self.disabled 69 | self.text_color = self.text_disabled 70 | else 71 | self.text_color = self.text_normal 72 | end 73 | end 74 | 75 | function Button:update(dt) 76 | if not self.interactible then return end 77 | 78 | local mx, my = love.mouse.getPosition() 79 | local left_click = love.mouse.isDown(1) 80 | local in_bounds = mouse_in_bounds(self, mx, my) 81 | 82 | if in_bounds and not left_click then 83 | if self.prev_left_click and self.color == self.pressed then 84 | _G.events:invoke("onBtnClick", self) 85 | end 86 | self.color = self.highlight 87 | elseif in_bounds and left_click and not self.prev_left_click then 88 | self.color = self.pressed 89 | elseif not in_bounds then 90 | self.color = self.normal 91 | end 92 | 93 | self.prev_left_click = left_click 94 | end 95 | 96 | function Button:draw() 97 | -- love.graphics.line(self.pos.x, self.pos.y - self.h / 2, self.pos.x, self.pos.y + self.h / 2) 98 | -- love.graphics.line(self.pos.x - self.w / 2, self.pos.y, self.pos.x + self.w / 2, self.pos.y) 99 | local r, g, b, a = love.graphics.getColor() 100 | love.graphics.setColor(self.color) 101 | love.graphics.rectangle("fill", self.pos.x - self.w / 2, self.pos.y - self.h / 2, self.w, self.h, 4, 4) 102 | 103 | local f = love.graphics.getFont() 104 | local _, lines = f:getWrap(self.text, self.w) 105 | local fh = f:getHeight() 106 | 107 | love.graphics.setColor(self.text_color) 108 | love.graphics.printf(self.text, self.pos.x - self.w / 2, self.pos.y - (fh /2 * #lines), self.w, "center") 109 | love.graphics.setColor(r, g, b, a) 110 | end 111 | 112 | return Button 113 | -------------------------------------------------------------------------------- /lib/ui/Checkbox.lua: -------------------------------------------------------------------------------- 1 | local U = require("lib.Utils") 2 | local Class = require("lib.Class") 3 | local Vector2 = require("lib.Vector2") 4 | 5 | local Checkbox = Class:derive("Checkbox") 6 | 7 | local line_width = 4 8 | local padding = 10 9 | local box_height_percentage = 0.9 10 | 11 | function Checkbox:new(x, y, w, h, text) 12 | self.pos = Vector2(x or 0, y or 0) 13 | self.w = w 14 | self.h = h 15 | self.text = text 16 | self.cb_height = self.h * box_height_percentage 17 | self.cb_width = self.cb_height 18 | self.checked = false 19 | 20 | --Text Colors 21 | self.normal = U.gray(0.5, 1) 22 | self.highlight = U.gray(0.78, 1) 23 | self.pressed = U.gray(1, 1) 24 | self.disabled = U.gray(0.5, 0.5) 25 | 26 | self.color = self.normal 27 | self.prev_left_click = false 28 | self.interactible = true 29 | end 30 | 31 | function Checkbox:colors(normal, highlight, pressed, disabled) 32 | assert(type(normal) == "table", "normal parameter must be a table!") 33 | assert(type(highlight) == "table", "highlight parameter must be a table!") 34 | assert(type(pressed) == "table", "pressed parameter must be a table!") 35 | --assert(type(disabled) == "table", "disabled parameter must be a table!") 36 | self.normal = normal 37 | self.highlight = highlight 38 | self.pressed = pressed 39 | self.disabled = disabled or self.disabled 40 | end 41 | 42 | function Checkbox:set_box(width, height) 43 | height = height or width 44 | self.cb_height = math.min(self.h, height) 45 | self.cb_width = math.min(self.w, width) 46 | end 47 | 48 | function Checkbox:enable(enabled) 49 | self.interactible = enabled 50 | if not enabled then 51 | self.color = self.disabled 52 | else 53 | self.color = self.normal 54 | end 55 | end 56 | 57 | --rx,ry is the upper left corner of the rectangle 58 | --rw,rh is the width and height of the rectangle 59 | -- function U.mouse_in_rect(mx, my, rx, ry, rw, rh) 60 | 61 | function Checkbox:update(dt) 62 | if not self.interactible then return end 63 | 64 | local mx, my = love.mouse.getPosition() 65 | local left_click = love.mouse.isDown(1) 66 | local in_bounds = U.mouse_in_rect(mx,my, self.pos.x, self.pos.y, self.w, self.h) 67 | 68 | if in_bounds and not left_click then 69 | if self.prev_left_click and self.color == self.pressed then 70 | self.checked = not self.checked 71 | _G.events:invoke("onCheckboxClicked", self, self.checked) 72 | end 73 | self.color = self.highlight 74 | elseif in_bounds and left_click and not self.prev_left_click then 75 | self.color = self.pressed 76 | elseif not in_bounds then 77 | self.color = self.normal 78 | end 79 | 80 | self.prev_left_click = left_click 81 | end 82 | 83 | function Checkbox:draw() 84 | local r, g, b, a = love.graphics.getColor() 85 | local f = love.graphics.getFont() 86 | local _, lines = f:getWrap(self.text, self.w - (self.cb_width + padding)) 87 | local fh = f:getHeight() 88 | local lw = love.graphics.getLineWidth() 89 | 90 | love.graphics.setColor(self.color) 91 | 92 | -- love.graphics.rectangle("line", self.pos.x, self.pos.y, self.w, self.h) 93 | love.graphics.setLineWidth(line_width) 94 | love.graphics.rectangle("line", self.pos.x, self.pos.y, self.cb_width, self.cb_height, 5, 5) 95 | 96 | if self.checked then 97 | love.graphics.rectangle("fill", self.pos.x + line_width, self.pos.y + line_width, self.cb_width - 2 * line_width, self.cb_height - 2 * line_width) 98 | end 99 | 100 | love.graphics.printf(self.text, self.pos.x + self.cb_width + padding, self.pos.y + self.h / 2 - (fh / 2 * #lines), self.w - (self.cb_width + padding), "left") 101 | 102 | love.graphics.setColor(r, g, b, a) 103 | love.graphics.setLineWidth(lw) 104 | end 105 | 106 | 107 | return Checkbox -------------------------------------------------------------------------------- /lib/ui/Label.lua: -------------------------------------------------------------------------------- 1 | local U = require("lib.Utils") 2 | local Class = require("lib.Class") 3 | local Vector2 = require("lib.Vector2") 4 | 5 | local Label = Class:derive("Label") 6 | 7 | function Label:new(x, y, w, h, text, color, align) 8 | self.pos = Vector2(x or 0, y or 0) 9 | self.w = w 10 | self.h = h 11 | self.text = text 12 | self.color = color or U.color(1) 13 | self.align = align or "center" 14 | end 15 | 16 | function Label:update(dt) end 17 | 18 | function Label:draw() 19 | local f = love.graphics.getFont() 20 | local _, lines = f:getWrap(self.text, self.w) 21 | local fh = f:getHeight() 22 | 23 | love.graphics.setColor(self.color) 24 | love.graphics.printf(self.text, self.pos.x, self.pos.y - (fh /2 * #lines), self.w, self.align) 25 | -- love.graphics.rectangle("line", self.pos.x, self.pos.y - self.h /2, self.w, self.h) 26 | end 27 | 28 | return Label -------------------------------------------------------------------------------- /lib/ui/Slider.lua: -------------------------------------------------------------------------------- 1 | local U = require("lib.Utils") 2 | local Class = require("lib.Class") 3 | local Vector2 = require("lib.Vector2") 4 | 5 | local Slider = Class:derive("Slider") 6 | 7 | local slider_size = 20 8 | local groove_size = 6 9 | 10 | function Slider:new(x, y, w, h, id, is_vertical) 11 | self.pos = Vector2(x or 0, y or 0) 12 | self.w = w 13 | self.h = h 14 | self.id = id or '' 15 | 16 | self.is_vertical = (is_vertical and true) or false 17 | self.slider_pos = 0 18 | self.slider_prev_pos = 0 19 | self.slider_delta = 0 20 | self.moving_slider = false 21 | 22 | --Goes between 0 and 100 or maybe 23 | -- a percentage as a float number 24 | self.value = 0 25 | 26 | --Slider Colors 27 | self.normal = U.color(0.5, 0.125, 0.125, 0.75) 28 | self.highlight = U.color(0.75, 0.125, 0.125, 1) 29 | self.pressed = U.color(1, 0.125, 0.125, 1) 30 | self.disabled = U.gray(0.5, 0.5) 31 | 32 | self.groove_color = U.gray(0.5) 33 | self.color = self.normal 34 | self.interactible = true 35 | end 36 | 37 | --Returns a value between 0 and 100 38 | function Slider:get_value() 39 | if self.is_vertical then 40 | return math.floor((self.slider_pos / (self.h - slider_size)) * 100) 41 | else 42 | return math.floor((self.slider_pos / (self.w - slider_size)) * 100) 43 | end 44 | end 45 | 46 | function Slider:update(dt) 47 | if not self.interactible then 48 | return 49 | end 50 | 51 | local mx, my = love.mouse.getPosition() 52 | local left_click = love.mouse.isDown(1) 53 | local in_bounds = false 54 | 55 | if self.is_vertical then 56 | in_bounds = U.mouse_in_rect(mx, my, self.pos.x, self.pos.y + self.h - self.slider_pos - slider_size, self.w, slider_size) 57 | else 58 | in_bounds = U.mouse_in_rect(mx, my, self.pos.x + self.slider_pos, self.pos.y - self.h, slider_size, self.h) 59 | end 60 | 61 | if in_bounds and not left_click then 62 | self.color = self.highlight 63 | elseif in_bounds and left_click then 64 | if not self.prev_left_click then 65 | if self.is_vertical then 66 | self.slider_delta = self.pos.y + self.h - self.slider_pos - my 67 | else 68 | self.slider_delta = self.slider_pos - mx 69 | end 70 | self.moving_slider = true 71 | end 72 | else 73 | self.color = self.normal 74 | end 75 | 76 | if self.moving_slider and left_click then 77 | self.color = self.pressed 78 | self.slider_prev_pos = self.slider_pos 79 | 80 | if self.is_vertical then 81 | self.slider_pos = self.pos.y + self.h - (my + self.slider_delta) 82 | if self.slider_pos > self.h - slider_size then 83 | self.slider_pos = self.h - slider_size 84 | elseif self.slider_pos < 0 then 85 | self.slider_pos = 0 86 | end 87 | else 88 | self.slider_pos = mx + self.slider_delta 89 | if self.slider_pos > self.w - slider_size then 90 | self.slider_pos = self.w - slider_size 91 | elseif self.slider_pos < 0 then 92 | self.slider_pos = 0 93 | end 94 | end 95 | 96 | if self.slider_prev_pos ~= self.slider_pos then 97 | _G.events:invoke("onSliderChanged", self) 98 | end 99 | elseif self.moving_slider and not left_click then 100 | self.moving_slider = false 101 | self.color = self.normal 102 | end 103 | 104 | self.prev_left_click = left_click 105 | end 106 | 107 | function Slider:draw() 108 | local r, g, b, a = love.graphics.getColor() 109 | love.graphics.setColor(self.groove_color) 110 | 111 | if self.is_vertical then 112 | love.graphics.rectangle("fill", self.pos.x + self.w / 2 - groove_size / 2, self.pos.y, groove_size, self.h, 2, 2) 113 | love.graphics.setColor(self.color) 114 | love.graphics.rectangle("fill", self.pos.x, self.pos.y + self.h - self.slider_pos - slider_size, self.w, slider_size, 2, 2) 115 | else 116 | love.graphics.rectangle("fill", self.pos.x, self.pos.y - self.h / 2 - groove_size / 2, self.w, groove_size, 2, 2) 117 | love.graphics.setColor(self.color) 118 | love.graphics.rectangle("fill", self.pos.x + self.slider_pos, self.pos.y - self.h, slider_size, self.h, 2, 2) 119 | end 120 | 121 | love.graphics.setColor(r, g, b, a) 122 | end 123 | 124 | return Slider 125 | -------------------------------------------------------------------------------- /lib/ui/TextField.lua: -------------------------------------------------------------------------------- 1 | local utf8 = require("utf8") 2 | local U = require("lib.Utils") 3 | local Label = require("lib.ui.Label") 4 | local TextField = Label:derive("TextField") 5 | 6 | local cursor = '|' 7 | 8 | --On Android and iOS, textinput is disabled by default; 9 | --call love.keyboard.setTextInput to enable it. 10 | 11 | function TextField:new(x, y, w, h, text, color, align) 12 | TextField.super.new(self, x, y, w, h, text, color, align) 13 | self.focus = false 14 | self.focused_color = U.color(0.5) 15 | self.unfocused_color = U.color(0.125) 16 | self.back_color = self.unfocused_color 17 | 18 | --TODO: Add capability to limit # of characters allowed in the input field 19 | --TODO: Add "enter" key detection too 20 | self.key_pressed = function(key) 21 | if key == "backspace" then 22 | self:on_text_input(key) 23 | end 24 | end 25 | self.text_input = function(text) 26 | self:on_text_input(text) 27 | end 28 | end 29 | 30 | function TextField:get_rect() 31 | return {x = self.pos.x, y = self.pos.y - self.h / 2, w = self.w, h = self.h} 32 | end 33 | 34 | function TextField:on_enter() 35 | _G.events:hook("key_pressed", self.key_pressed) 36 | _G.events:hook("text_input", self.text_input) 37 | end 38 | 39 | function TextField:on_exit() 40 | _G.events:unhook("key_pressed", self.key_pressed) 41 | _G.events:unhook("text_input", self.text_input) 42 | end 43 | 44 | function TextField:set_focus(focus) 45 | assert(type(focus) == 'boolean', "focus should be of type boolean") 46 | if focus then 47 | self.back_color = self.focused_color 48 | 49 | if not self.focus then 50 | self.text = self.text .. cursor 51 | end 52 | else 53 | self.back_color = self.unfocused_color 54 | if not focus and self.focus then 55 | self:remove_end_chars(1) 56 | end 57 | end 58 | self.focus = focus 59 | end 60 | 61 | function TextField:on_text_input(text) 62 | if not self.focus or not self.enabled then 63 | return 64 | end 65 | 66 | if text == "backspace" then 67 | self:remove_end_chars(2) 68 | self.text = self.text .. cursor 69 | else 70 | self:remove_end_chars(1) 71 | self.text = self.text .. text 72 | self.text = self.text .. cursor 73 | end 74 | end 75 | 76 | function TextField:remove_end_chars(num) 77 | -- get the byte offset to the last UTF-8 character in the string. 78 | local byteoffset = utf8.offset(self.text, -num) 79 | 80 | if byteoffset then 81 | -- remove the last UTF-8 character. 82 | -- string.sub operates on bytes rather than UTF-8 characters, 83 | --so we couldn't do string.sub(text, 1, -2). 84 | self.text = string.sub(self.text, 1, byteoffset - 1) 85 | end 86 | end 87 | 88 | function TextField:draw() 89 | love.graphics.setColor(self.back_color) 90 | love.graphics.rectangle("fill", self.pos.x, self.pos.y - self.h / 2, self.w, self.h) 91 | TextField.super.draw(self) 92 | end 93 | 94 | return TextField 95 | -------------------------------------------------------------------------------- /main.lua: -------------------------------------------------------------------------------- 1 | Key = require("lib.Keyboard") 2 | Tween = require("lib.Tween") 3 | 4 | local GPM = require("lib.GamepadMgr") 5 | local SM = require("lib.SceneMgr") 6 | local Event = require("lib.Events") 7 | 8 | local sm 9 | 10 | local gpm = GPM({"assets/gamecontrollerdb.txt"}) 11 | 12 | function love.load() 13 | --Love2D game settings 14 | love.graphics.setDefaultFilter('nearest', 'nearest') 15 | local font = love.graphics.newFont("assets/SuperMario256.ttf", 20) 16 | --set the font to the one above 17 | love.graphics.setFont(font) 18 | 19 | _G.events = Event(false) 20 | 21 | Key:hook_love_events() 22 | 23 | gpm.event:hook('controller_added', on_controller_added) 24 | gpm.event:hook('controller_removed', on_controller_removed) 25 | 26 | sm = SM("scenes", {"MainMenu", "Test", "TweenTest"}) 27 | --sm:switch("MainMenu") 28 | --sm:switch("Test") 29 | sm:switch("TweenTest") 30 | end 31 | 32 | function on_controller_added(joyId) 33 | print("controller " .. joyId .. "added") 34 | end 35 | 36 | function on_controller_removed(joyId) 37 | print("controller " .. joyId .. "removed") 38 | end 39 | 40 | function love.update(dt) 41 | if dt > 0.035 then return end 42 | 43 | -- if Key:key_down(",") then 44 | -- sm:switch("MainMenu") 45 | -- elseif Key:key_down(".") then 46 | -- sm:switch("Test") 47 | -- end 48 | 49 | sm:update(dt) 50 | Key:update(dt) 51 | gpm:update(dt) 52 | Tween.update(dt) 53 | end 54 | 55 | function love.draw() 56 | sm:draw() 57 | end 58 | -------------------------------------------------------------------------------- /scenes/MainMenu.lua: -------------------------------------------------------------------------------- 1 | local Scene = require("lib.Scene") 2 | local Button = require("lib.ui.Button") 3 | local Label = require("lib.ui.Label") 4 | local U = require("lib.Utils") 5 | local TextField = require("lib.ui.TextField") 6 | local Slider = require("lib.ui.Slider") 7 | local Checkbox = require("lib.ui.Checkbox") 8 | local Bar = require("lib.ui.Bar") 9 | 10 | local MM = Scene:derive("MainMenu") 11 | local menutxt 12 | 13 | function MM:new(scene_mgr) 14 | MM.super.new(self, scene_mgr) 15 | 16 | local sw = love.graphics.getWidth() 17 | local sh = love.graphics.getHeight() 18 | 19 | local start_button = Button(sw / 2, sh / 2 - 30, 140, 40, "Start") 20 | local exit_button = Button(sw / 2, sh / 2 + 30, 140, 40, "Exit") 21 | exit_button:colors({0, 128, 0, 255}, {64, 212, 64, 255}, {200, 255, 200, 255}) 22 | 23 | menutxt = Label(love.graphics.getWidth() + 20, 20, love.graphics.getWidth(), 40, "Main Menu") 24 | 25 | self.tf = TextField(love.graphics.getWidth() / 2- 50, 60, 100, 40, "hello", U.gray(196), "left") 26 | 27 | self.slider = Slider(love.graphics.getWidth() / 2 - 100 , 125, 200, 40, "volume") 28 | self.vslider = Slider(20, 40 , 40, 200, "test", true) 29 | self.label = Label(425, 105, 250, 40, "0", U.gray(255), "left"); 30 | self.vlabel = Label(12, 20, 60, 40, "0", U.gray(255), "center"); 31 | self.cb = Checkbox(love.graphics.getWidth() / 2 - 100, 250, 200, 40, "Enable Music") 32 | 33 | self.bar = Bar("health", love.graphics.getWidth() / 2, love.graphics.getHeight() - 40, 200, 40, "0%") 34 | 35 | self.em:add(start_button) 36 | self.em:add(exit_button) 37 | self.em:add(menutxt) 38 | self.em:add(self.tf) 39 | self.em:add(self.slider) 40 | self.em:add(self.vslider) 41 | self.em:add(self.label) 42 | self.em:add(self.vlabel) 43 | self.em:add(self.cb) 44 | self.em:add(self.bar) 45 | 46 | self.click = function(btn) self:on_click(btn) end 47 | self.slider_changed = function(slider) self:on_slider_changed(slider) end 48 | self.checkbox_changed = function(checkbox, value) self:on_checkbox_changed(checkbox, value) end 49 | self.bar_changed = function(bar, value) self:on_bar_changed(bar, value) end 50 | end 51 | 52 | local entered = false 53 | function MM:enter() 54 | Tween.create(menutxt.pos, "x", 0, 1, Tween.cubic_out) 55 | MM.super.enter(self) 56 | _G.events:hook("onBtnClick", self.click) 57 | _G.events:hook("onSliderChanged", self.slider_changed) 58 | _G.events:hook("onCheckboxClicked", self.checkbox_changed) 59 | _G.events:hook("onBarChanged", self.bar_changed) 60 | end 61 | 62 | function MM:exit() 63 | MM.super.exit(self) 64 | _G.events:unhook("onBtnClick", self.click) 65 | _G.events:unhook("onSliderChanged", self.slider_changed) 66 | _G.events:unhook("onCheckboxClicked", self.checkbox_changed) 67 | _G.events:unhook("onBarChanged", self.bar_changed) 68 | end 69 | 70 | function MM:on_checkbox_changed(checkbox, value) 71 | -- if checkbox.text == "Enable Music" then 72 | print(checkbox.text .." : " .. tostring(value)) 73 | -- end 74 | end 75 | 76 | function MM:on_bar_changed(bar, value) 77 | bar.text = tostring(value .. "%") 78 | if value == 100 then 79 | bar.fill_color = U.color(0,255,0,255) 80 | end 81 | end 82 | 83 | function MM:on_slider_changed(slider) 84 | if slider.id == "volume" then 85 | self.label.text = slider:get_value() 86 | elseif slider.id == "test" then 87 | self.vlabel.text = slider:get_value() 88 | end 89 | end 90 | 91 | function MM:on_click(button) 92 | if button.text == "Start" then 93 | self.scene_mgr:switch("Test") 94 | elseif button.text == "Exit" then 95 | love.event.quit() 96 | end 97 | end 98 | 99 | local prev_down = false 100 | function MM:update(dt) 101 | self.super.update(self,dt) 102 | 103 | if Key:key_down("escape") then 104 | love.event.quit() 105 | elseif Key:key_down("e") then 106 | self.bar:set(self.bar.percentage + 5) 107 | elseif Key:key_down("q") then 108 | self.bar:set(self.bar.percentage - 5) 109 | -- elseif Key:key_down("space") then 110 | -- self.button:enable(not self.button.interactible) 111 | end 112 | 113 | --mouse stuff 114 | local mx,my = love.mouse.getPosition() 115 | local down = love.mouse.isDown(1) 116 | 117 | --if the mouse left btn was just clicked... 118 | if down and not prev_down then 119 | if U.point_in_rect({x=mx, y=my}, self.tf:get_rect()) then 120 | self.tf:set_focus(true) 121 | else 122 | self.tf:set_focus(false) 123 | end 124 | end 125 | 126 | prev_down = down 127 | 128 | end 129 | 130 | return MM 131 | -------------------------------------------------------------------------------- /scenes/Test.lua: -------------------------------------------------------------------------------- 1 | local Scene = require("lib.Scene") 2 | local U = require("lib.Utils") 3 | local Vector2 = require("lib.Vector2") 4 | local Vector3 = require("lib.Vector3") 5 | 6 | local Entity = require("lib.Entity") 7 | local Transform = require("lib.components.Transform") 8 | 9 | local CC = require("lib.components.physics.CircleCollider") 10 | local PC = require("lib.components.physics.PolygonCollider") 11 | 12 | local Player = require("../Player") 13 | local Missile = require("../Missile") 14 | local Sat = require("lib.Sat") 15 | 16 | local T = Scene:derive("Test") 17 | 18 | function T:new(scene_mgr) 19 | T.super.new(self, scene_mgr) 20 | 21 | --Player Entitiy 22 | self.p = Entity(Transform(100, 100, 4, 4), Player(), Player.create_sprite(),CC(32,32), 23 | PC({Vector2(-8,-8), Vector2(8,-8), Vector2(8,8), Vector2(-8, 8)})) 24 | 25 | self.em:add(self.p) 26 | 27 | --Missile Entity 28 | self.e = Entity(Transform(350, 100, 1, 1, 0), Missile(), Missile.create_sprite(),CC(62,40), 29 | PC({Vector2(-62,-40), Vector2(62,-40), Vector2(62,40), Vector2(-62, 40)})) 30 | self.em:add(self.e) 31 | 32 | --Set the missile's target to the player 33 | self.e.Missile:target(self.p.Transform) 34 | end 35 | 36 | function T:update(dt) 37 | self.super.update(self,dt) 38 | 39 | if Key:key_down("escape") then 40 | love.event.quit() 41 | end 42 | 43 | local msuv, amount = Sat.Collide(self.p.PolygonCollider.world_vertices, self.e.PolygonCollider.world_vertices) 44 | if msuv ~= nil then 45 | --print(string.format("min sep unit vector: %.2f, %.2f amount: %.2f",msuv.x,msuv.y, amount)) 46 | local sepDir = Vector2(self.p.Transform.x - self.e.Transform.x, self.p.Transform.y - self.e.Transform.y) 47 | 48 | --Swap signs on the Minimum separation unit vector as required to push self.p in the correct direction 49 | if not U.same_sign(sepDir.x, msuv.x) then msuv.x = msuv.x * -1 end 50 | if not U.same_sign(sepDir.y, msuv.y) then msuv.y = msuv.y * -1 end 51 | 52 | self.p.Transform.x = self.p.Transform.x + msuv.x * amount 53 | self.p.Transform.y = self.p.Transform.y + msuv.y * amount 54 | end 55 | 56 | end 57 | 58 | function T:draw() 59 | love.graphics.clear(0.25,0.25,1) 60 | self.super.draw(self) 61 | 62 | -- local triangle = {100, 100, 200, 100, 150, 305} 63 | -- local rect = {100, 300, 200, 300, 200, 350, 100, 350} 64 | 65 | -- local msuv, amount = Sat.Collide(Sat.to_Vector2_array(triangle), Sat.to_Vector2_array(rect)) 66 | -- if msuv ~= nil then 67 | -- print("min sep unit vector: " .. msuv.x .. "," .. msuv.y .. " sep amount:" .. amount) 68 | -- end 69 | 70 | -- love.graphics.polygon("line", triangle) 71 | -- love.graphics.polygon("line", rect) 72 | end 73 | 74 | return T 75 | -------------------------------------------------------------------------------- /scenes/TweenTest.lua: -------------------------------------------------------------------------------- 1 | local Scene = require("lib.Scene") 2 | local Label = require("lib.ui.Label") 3 | local Player = require("../Player") 4 | 5 | local T = Scene:derive("TweenTest") 6 | 7 | local lbl 8 | local pos = {x = 0, y = 150} 9 | 10 | local tween_index = 1 11 | local available_tweens = {} 12 | 13 | function get_tweens() 14 | for k,v in pairs(Tween) do 15 | if type(v) == "function" then 16 | if k ~= "update" and k ~= "create" then 17 | available_tweens[#available_tweens + 1] = v 18 | end 19 | end 20 | end 21 | end 22 | 23 | function T:new(scene_mgr) 24 | T.super.new(self, scene_mgr) 25 | self.lbl = Label(0,15, love.graphics.getWidth(), 50, "not a tween") 26 | self.lbl2 = Label(0,love.graphics.getHeight() - 45, love.graphics.getWidth(), 50, "left/right arrows: cycle tween type") 27 | self.lbl3 = Label(0,love.graphics.getHeight() - 20, love.graphics.getWidth(), 50, "spacebar: execute tween") 28 | self.em:add(self.lbl) 29 | self.em:add(self.lbl2) 30 | self.em:add(self.lbl3) 31 | ease_function = Tween.quad_out 32 | pos.x = love.graphics.getWidth() / 2 - 150 33 | get_tweens() 34 | end 35 | 36 | function T:update(dt) 37 | self.super.update(self,dt) 38 | 39 | if Key:key_down("escape") then 40 | love.event.quit() 41 | elseif Key:key_down("space") then 42 | if self.t ~= nil then 43 | self.t:cancel() 44 | end 45 | 46 | local xstart = love.graphics.getWidth() / 2 - 150 47 | local xend = love.graphics.getWidth() / 2 + 150 48 | 49 | if self.t == nil or self.t.to == xstart then 50 | self.t = Tween.create(pos, "x", xend, 1, available_tweens[tween_index]) 51 | self.t.on_complete = on_complete 52 | elseif self.t ~= nill and self.t.to == xend then 53 | self.t = Tween.create(pos, "x", xstart, 1, available_tweens[tween_index]) 54 | self.t.on_complete = on_complete 55 | end 56 | 57 | elseif Key:key_down("right") then 58 | tween_index = tween_index + 1 59 | if tween_index > #available_tweens then 60 | tween_index = 1 61 | end 62 | elseif Key:key_down("left") then 63 | tween_index = tween_index - 1 64 | if tween_index < 1 then 65 | tween_index = #available_tweens 66 | end 67 | end 68 | 69 | for k,v in pairs(Tween) do 70 | if v == available_tweens[tween_index] then 71 | self.lbl.text = k 72 | break 73 | end 74 | end 75 | end 76 | 77 | function T:draw() 78 | love.graphics.clear(0.25,0.25,0.25) 79 | self.super.draw(self) 80 | 81 | love.graphics.rectangle("fill", pos.x, pos.y, 30,30) 82 | end 83 | 84 | return T 85 | -------------------------------------------------------------------------------- /test.lua: -------------------------------------------------------------------------------- 1 | Class = require "Class" 2 | 3 | local Animal = Class:derive("Animal") 4 | local Human = Class:derive("Human") 5 | 6 | function Animal:SoundOff() 7 | print("uh?") 8 | end 9 | 10 | local a = Animal() 11 | a:SoundOff() 12 | print(a:get_type()) 13 | 14 | local Cat = Animal:derive("Cat") 15 | 16 | function Cat:SoundOff() 17 | print("Meow!") 18 | end 19 | 20 | local c = Cat() 21 | c:SoundOff() 22 | print(c:get_type()) 23 | 24 | local Minx = Cat:derive("Minx") 25 | local m = Minx() 26 | -- print(m:is(Human)) 27 | print(m:is_type("Minx")) 28 | 29 | local function repeats(str, num) return num > 0 and str .. repeats(str, num -1) or "" end 30 | local function dump(o, indent) 31 | indent = indent or 0 32 | if type(o) == 'table' then 33 | local s = '\n' .. repeats(" ", indent) .. '{\n' 34 | for k,v in pairs(o) do 35 | s = s .. repeats(" ", indent) 36 | if type(k) ~= 'number' then k = '"'..k..'"' end 37 | if o ~= v then --Don't want circular loops 38 | s = s .. '['..k..'] = ' .. dump(v, indent + 1) .. '\n' 39 | end 40 | end 41 | return s ..repeats(" ", indent) .. '}\n' 42 | else 43 | return tostring(o) 44 | end 45 | end --------------------------------------------------------------------------------