├── .github ├── FUNDING.yml └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── LICENSE ├── README.md ├── cfg ├── binds.lst └── binds_default.cfg ├── class.lua ├── common ├── color.lua └── vector.lua ├── conf.lua ├── engine ├── client │ ├── bind.lua │ ├── camera.lua │ ├── canvas.lua │ ├── chat.lua │ ├── debugoverlay.lua │ ├── graphics.lua │ ├── gui │ │ ├── autoloader.lua │ │ ├── box │ │ │ ├── init.lua │ │ │ └── properties.lua │ │ ├── button.lua │ │ ├── checkbox.lua │ │ ├── closebutton.lua │ │ ├── commandbutton.lua │ │ ├── commandbuttongroup.lua │ │ ├── console │ │ │ ├── init.lua │ │ │ ├── textbox.lua │ │ │ └── textboxautocompleteitemgroup.lua │ │ ├── debugoverlaypanel.lua │ │ ├── dropdownlist.lua │ │ ├── dropdownlistitem.lua │ │ ├── dropdownlistitemgroup.lua │ │ ├── frame.lua │ │ ├── framerate.lua │ │ ├── frametab.lua │ │ ├── frametabgroup.lua │ │ ├── frametabpanel.lua │ │ ├── frametabpanels.lua │ │ ├── handlers.lua │ │ ├── hudframe.lua │ │ ├── hudprofiler.lua │ │ ├── hudvoice.lua │ │ ├── imagepanel.lua │ │ ├── init.lua │ │ ├── label.lua │ │ ├── netgraph.lua │ │ ├── optionsmenu │ │ │ ├── audiooptionspanel.lua │ │ │ ├── bindlistheader.lua │ │ │ ├── bindlistitem.lua │ │ │ ├── bindlistpanel.lua │ │ │ ├── init.lua │ │ │ ├── keyboardoptionsadvancedframe.lua │ │ │ ├── keyboardoptionscommandbuttongroup.lua │ │ │ ├── keyboardoptionspanel.lua │ │ │ ├── multiplayeroptionspanel.lua │ │ │ └── videooptionspanel.lua │ │ ├── panel.lua │ │ ├── passwordtextbox.lua │ │ ├── progressbar.lua │ │ ├── radiobutton.lua │ │ ├── radiobuttongroup.lua │ │ ├── rootpanel.lua │ │ ├── scheme.lua │ │ ├── scrollablepanel.lua │ │ ├── scrollbar.lua │ │ ├── slider.lua │ │ ├── tabbedframe.lua │ │ ├── testframe.lua │ │ ├── text.lua │ │ ├── textbox.lua │ │ ├── textboxautocompleteitemgroup.lua │ │ ├── throbber.lua │ │ ├── viewport.lua │ │ └── watch.lua │ ├── handlers.lua │ ├── init.lua │ ├── input.lua │ ├── network │ │ ├── init.lua │ │ ├── localhost_enet_peer.lua │ │ └── localhost_enet_server.lua │ ├── payloads.lua │ ├── source.lua │ └── sprite.lua ├── init.lua ├── server │ ├── handlers.lua │ ├── init.lua │ ├── network │ │ ├── host.lua │ │ └── init.lua │ └── payloads.lua └── shared │ ├── addon.lua │ ├── baselib.lua │ ├── buttons.lua │ ├── concommand.lua │ ├── config.lua │ ├── convar.lua │ ├── dblib.lua │ ├── entities │ ├── character.lua │ ├── entity.lua │ ├── init.lua │ ├── networkvar.lua │ ├── npc.lua │ ├── player.lua │ ├── trigger.lua │ ├── trigger_changelevel.lua │ └── trigger_transition.lua │ ├── heaplib.lua │ ├── hook.lua │ ├── loadlib.lua │ ├── map │ ├── init.lua │ ├── layer.lua │ └── tileset.lua │ ├── mathlib.lua │ ├── network │ ├── payload.lua │ └── payloads.lua │ ├── path │ ├── init.lua │ └── node.lua │ ├── profile.lua │ ├── strlib.lua │ ├── tablib.lua │ ├── tween.lua │ └── typelenvalues.lua ├── fonts ├── SourceCodePro-Light.otf ├── SourceSansPro-Bold.otf ├── SourceSansPro-Light.otf └── SourceSansPro-Regular.otf ├── game ├── client │ ├── gui │ │ ├── closedialog.lua │ │ ├── hudabout.lua │ │ ├── hudchat.lua │ │ ├── hudchattextbox.lua │ │ ├── huddialogue.lua │ │ ├── hudgamemenu │ │ │ ├── init.lua │ │ │ ├── inventory.lua │ │ │ ├── itembutton.lua │ │ │ ├── itemgrid.lua │ │ │ ├── navigation.lua │ │ │ ├── navigationbutton.lua │ │ │ ├── stat.lua │ │ │ └── stats.lua │ │ ├── hudhealth.lua │ │ ├── hudmana.lua │ │ ├── hudmoveindicator.lua │ │ ├── hudspeechballoons.lua │ │ ├── mainmenu.lua │ │ ├── mainmenubutton.lua │ │ ├── mainmenuclosebutton.lua │ │ ├── optionsitem.lua │ │ └── optionsitemgroup.lua │ └── init.lua ├── init.lua ├── server │ └── init.lua └── shared │ └── entities │ ├── func_examine.lua │ ├── item.lua │ ├── item_apple.lua │ ├── item_gold.lua │ ├── prop_chest.lua │ ├── prop_ore_rock.lua │ ├── prop_tree.lua │ ├── prop_worldgate_spawn.lua │ ├── vanpc.lua │ ├── vaplayer.lua │ ├── weapon_bow.lua │ └── weapon_staff.lua ├── images ├── entities │ ├── item_apple │ │ ├── 1.png │ │ └── 2.png │ ├── prop_chest.png │ ├── prop_ore_rock.png │ ├── prop_tree │ │ └── 1.png │ ├── prop_worldgate_spawn.png │ ├── weapon_bow.png │ └── weapon_staff.png ├── error.png ├── gui │ ├── arrow_down.png │ ├── arrow_down@2x.png │ ├── check.png │ ├── check@2x.png │ ├── close.png │ ├── close@2x.png │ ├── close_large.png │ ├── close_large@2x.png │ ├── logo.png │ ├── logo@2x.png │ ├── logo_dark.png │ ├── logo_dark@2x.png │ ├── logo_small.png │ ├── logo_small@2x.png │ ├── radiobutton_foreground.png │ ├── radiobutton_foreground@2x.png │ ├── selection_dot.png │ ├── selection_dot@2x.png │ ├── throbber.png │ └── throbber@2x.png ├── icon.png ├── icon_osx.png ├── moveindicator.lua ├── moveindicator.png ├── player.lua ├── player.png └── tilesets │ ├── architect.png │ ├── developer.png │ └── world.png ├── init.lua ├── main.lua ├── maps ├── Architect.tsx ├── Developer.tsx ├── World.tsx ├── test_01.lua └── test_01.tmx ├── public ├── json.lua ├── md5.lua ├── utf8.lua └── utf8data.lua ├── schemes ├── Chat.lua ├── Console.lua └── Default.lua ├── scripts └── test │ ├── box.lua │ ├── line.lua │ └── textbox.lua ├── shaders ├── coloroverlay.frag ├── gaussianblur.frag ├── gaussianblur.lua ├── shader.lua └── stroke.frag └── sounds └── footsteps ├── grassleft.lua ├── grassleft.wav ├── grassright.lua └── grassright.wav /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: # [andrewmcwatters] 4 | patreon: # Planimeter 5 | open_collective: # Replace with a single Open Collective username 6 | ko_fi: # Replace with a single Ko-fi username 7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel 8 | custom: # Replace with a single custom sponsorship URL 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Thumbs.db 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Planimeter 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 | -------------------------------------------------------------------------------- /cfg/binds.lst: -------------------------------------------------------------------------------- 1 | Movement 2 | ======== 3 | 4 | "Move forward" +forward 5 | "Move back" +back 6 | "Move left" +left 7 | "Move right" +right 8 | "Sprint" +speed 9 | 10 | Player 11 | ====== 12 | 13 | "Use" +use 14 | "Game Menu" +gamemenu 15 | "Zoom In" zoomin 16 | "Zoom Out" zoomout 17 | 18 | Communication 19 | ============= 20 | 21 | "Chat" chat 22 | "Voice" +voice 23 | 24 | Development 25 | =========== 26 | 27 | "Console" toggleconsole 28 | -------------------------------------------------------------------------------- /cfg/binds_default.cfg: -------------------------------------------------------------------------------- 1 | w +forward 2 | s +back 3 | a +left 4 | d +right 5 | lshift +speed 6 | e +use 7 | tab +gamemenu 8 | wu zoomin 9 | wd zoomout 10 | y chat 11 | k +voice 12 | \ toggleconsole 13 | -------------------------------------------------------------------------------- /common/color.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Color class 4 | -- 5 | --==========================================================================-- 6 | 7 | class( "color" ) 8 | 9 | function color.copy( c ) 10 | return color( unpack( c ) ) 11 | end 12 | 13 | function color:color( r, g, b, a ) 14 | if ( type( r ) == "color" ) then 15 | self[ 1 ] = r[ 1 ] or 0 16 | self[ 2 ] = r[ 2 ] or 0 17 | self[ 3 ] = r[ 3 ] or 0 18 | self[ 4 ] = g and g / 255 or ( r[ 4 ] or 0 ) 19 | return 20 | end 21 | 22 | self[ 1 ] = r / 255 or 0 23 | self[ 2 ] = g / 255 or 0 24 | self[ 3 ] = b / 255 or 0 25 | self[ 4 ] = a / 255 or 0 26 | end 27 | 28 | function color.__eq( a, b ) 29 | return a[ 1 ] == b[ 1 ] and 30 | a[ 2 ] == b[ 2 ] and 31 | a[ 3 ] == b[ 3 ] and 32 | a[ 4 ] == b[ 4 ] 33 | end 34 | 35 | function color:__tostring() 36 | return "color: (" .. 37 | self[ 1 ] .. ", " .. 38 | self[ 2 ] .. ", " .. 39 | self[ 3 ] .. ", " .. 40 | self[ 4 ] .. 41 | ")" 42 | end 43 | 44 | color.transparent = color( 0, 0, 0, 0 ) 45 | color.white = color( 255, 255, 255, 255 ) 46 | color.black = color( 0, 0, 0, 255 ) 47 | color.red = color( 255, 0, 0, 255 ) 48 | 49 | color.client = color( 168, 168, 123, 255 ) 50 | color.server = color( 123, 158, 168, 255 ) 51 | 52 | color.margin = color( 235, 179, 116, 167 ) 53 | color.padding = color( 157, 194, 132, 167 ) 54 | color.content = color( 122, 168, 215, 167 ) 55 | -------------------------------------------------------------------------------- /common/vector.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Vector class 4 | -- 5 | --==========================================================================-- 6 | 7 | class( "vector" ) 8 | 9 | function vector.copy( v ) 10 | return vector( v.x, v.y ) 11 | end 12 | 13 | function vector:vector( x, y ) 14 | self.x = x or 0 15 | self.y = y or 0 16 | end 17 | 18 | function vector:approximately( b ) 19 | return math.approximately( self.x, b.x ) and 20 | math.approximately( self.y, b.y ) 21 | end 22 | 23 | function vector:dot( b ) 24 | local ret = 0 25 | ret = ret + self.x * b.x 26 | ret = ret + self.y * b.y 27 | return ret 28 | end 29 | 30 | function vector:length() 31 | return math.sqrt( self:lengthSqr() ) 32 | end 33 | 34 | function vector:lengthSqr() 35 | return self.x ^ 2 + self.y ^ 2 36 | end 37 | 38 | function vector:normalize() 39 | local length = self:length() 40 | if ( length == 0 ) then 41 | return vector() 42 | end 43 | 44 | return vector( self.x / length, self.y / length ) 45 | end 46 | 47 | function vector:normalizeInPlace() 48 | local length = self:length() 49 | self.x = length == 0 and 0 or self.x / length 50 | self.y = length == 0 and 0 or self.y / length 51 | end 52 | 53 | function vector:toAngle() 54 | return math.atan2( self.y, self.x ) 55 | end 56 | 57 | function vector.__add( a, b ) 58 | return vector( a.x + b.x, a.y + b.y ) 59 | end 60 | 61 | function vector.__sub( a, b ) 62 | return vector( a.x - b.x, a.y - b.y ) 63 | end 64 | 65 | function vector.__mul( a, b ) 66 | if ( type( a ) == "number" ) then 67 | return vector( a * b.x, a * b.y ) 68 | elseif ( type( b ) == "number" ) then 69 | return vector( b * a.x, b * a.y ) 70 | else 71 | return vector( a.x * b.x, a.y * b.y ) 72 | end 73 | end 74 | 75 | function vector.__div( a, b ) 76 | if ( type( b ) == "number" ) then 77 | return vector( a.x / b, a.y / b ) 78 | else 79 | return vector( a.x / b.x, a.y / b.y ) 80 | end 81 | end 82 | 83 | function vector.__eq( a, b ) 84 | return a.x == b.x and a.y == b.y 85 | end 86 | 87 | function vector:__tostring() 88 | return "vector: (" .. self.x .. ", " .. self.y .. ")" 89 | end 90 | 91 | vector.origin = vector() 92 | -------------------------------------------------------------------------------- /conf.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: 4 | -- 5 | --==========================================================================-- 6 | 7 | require( "engine.shared.profile" ) 8 | profile.push( "load" ) 9 | 10 | argv = {} 11 | for _, v in ipairs( arg ) do argv[ v ] = true end 12 | 13 | if ( argv[ "--debug" ] ) then 14 | _DEBUG = true 15 | end 16 | 17 | if ( argv[ "--dedicated" ] ) then 18 | _SERVER = true 19 | _DEDICATED = true 20 | end 21 | 22 | if ( not _SERVER ) then 23 | _CLIENT = true 24 | end 25 | 26 | function love.conf( c ) 27 | c.title = "Grid Engine" 28 | c.version = "11.3" 29 | if ( _DEDICATED ) then 30 | c.modules.keyboard = false 31 | c.modules.mouse = false 32 | c.modules.joystick = false 33 | c.modules.touch = false 34 | c.modules.image = false 35 | c.modules.graphics = false 36 | c.modules.audio = false 37 | c.modules.sound = false 38 | c.modules.system = false 39 | c.modules.font = false 40 | c.modules.window = false 41 | c.modules.video = false 42 | else 43 | c.window.icon = "images/icon.png" 44 | require( "love.system" ) 45 | if ( love.system.getOS() == "OS X" ) then 46 | c.window.icon = "images/icon_osx.png" 47 | end 48 | c.window.resizable = true 49 | end 50 | c.identity = "grid" 51 | 52 | require( "engine.shared.loadlib" ) 53 | require( "engine.shared.config" ) 54 | config.load( c ) 55 | end 56 | -------------------------------------------------------------------------------- /engine/client/canvas.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. =============-- 2 | -- 3 | -- Purpose: Canvas class 4 | -- 5 | --============================================================================-- 6 | 7 | class( "canvas" ) 8 | 9 | canvas._canvases = canvas._canvases or {} 10 | 11 | local function copy( k ) 12 | if ( string.find( k, "__" ) == 1 ) then 13 | return 14 | end 15 | 16 | canvas[ k ] = function( self, ... ) 17 | local self = self._canvas 18 | return self[ k ]( self, ... ) 19 | end 20 | end 21 | 22 | local _R = debug.getregistry() 23 | for k in pairs( _R.Canvas ) do 24 | copy( k ) 25 | end 26 | 27 | local function newCanvas( self, ... ) 28 | self._canvas = love.graphics.newCanvas( ... ) 29 | end 30 | 31 | function canvas.invalidateCanvases() 32 | for _, v in ipairs( canvas._canvases ) do 33 | if ( typeof( v, "fullscreencanvas" ) ) then 34 | newCanvas( v, unpack( v._args ) ) 35 | end 36 | 37 | if ( v:shouldAutoRedraw() ) then 38 | v:invalidate() 39 | end 40 | end 41 | end 42 | 43 | local function noop() 44 | end 45 | 46 | function canvas:canvas( ... ) 47 | self._args = { ... } 48 | self._drawFunc = noop 49 | self.needsRedraw = false 50 | self.autoRedraw = true 51 | table.insert( canvas._canvases, self ) 52 | 53 | newCanvas( self, ... ) 54 | 55 | setproxy( self ) 56 | end 57 | 58 | local function render( self ) 59 | local canvas = love.graphics.getCanvas() 60 | love.graphics.setCanvas( { self._canvas, stencil = true } ) 61 | self:_drawFunc() 62 | love.graphics.setCanvas( canvas ) 63 | end 64 | 65 | function canvas:draw( ... ) 66 | if ( self.needsRedraw ) then 67 | local b = love.graphics.getBlendMode() 68 | love.graphics.setBlendMode( "alpha", "alphamultiply" ) 69 | render( self ) 70 | love.graphics.setBlendMode( b ) 71 | self.needsRedraw = false 72 | end 73 | 74 | love.graphics.draw( self._canvas, ... ) 75 | end 76 | 77 | function canvas:invalidate() 78 | self.needsRedraw = true 79 | end 80 | 81 | function canvas:remove() 82 | for i, v in ipairs( canvas._canvases ) do 83 | if ( v == self ) then 84 | table.remove( canvas._canvases, i ) 85 | end 86 | end 87 | 88 | collectgarbage() 89 | collectgarbage() 90 | end 91 | 92 | function canvas:renderTo( func ) 93 | self._drawFunc = func 94 | render( self ) 95 | end 96 | 97 | accessor( canvas, "autoRedraw", "should" ) 98 | 99 | function canvas:__tostring() 100 | local t = getmetatable( self ) 101 | setmetatable( self, {} ) 102 | local s = string.gsub( tostring( self ), "table", "canvas" ) 103 | setmetatable( self, t ) 104 | return s 105 | end 106 | 107 | canvas.__gc = canvas.remove 108 | 109 | class "fullscreencanvas" ( "canvas" ) 110 | 111 | function fullscreencanvas:fullscreencanvas( ... ) 112 | canvas.canvas( self, ... ) 113 | end 114 | 115 | fullscreencanvas.__gc = canvas.remove 116 | -------------------------------------------------------------------------------- /engine/client/chat.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Chat interface 4 | -- 5 | --==========================================================================-- 6 | 7 | local love = love 8 | local math = math 9 | local string = string 10 | local table = table 11 | local tostring = tostring 12 | local _G = _G 13 | 14 | module( "chat" ) 15 | 16 | function addText( ... ) 17 | if ( _G.g_Chat == nil ) then 18 | return 19 | end 20 | 21 | local args = { ... } 22 | table.tostring( args ) 23 | 24 | local chat = _G.g_Chat.output 25 | local text = table.concat( args, "\t" ) 26 | chat:activate() 27 | chat:insertText( text .. "\n" ) 28 | 29 | local readingtime = math.max( string.readingtime( text ), 5 ) 30 | chat:setHideTime( love.timer.getTime() + readingtime ) 31 | end 32 | -------------------------------------------------------------------------------- /engine/client/debugoverlay.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Debug Overlay interface 4 | -- 5 | --==========================================================================-- 6 | 7 | local _G = _G 8 | 9 | module( "debugoverlay" ) 10 | 11 | function line( worldIndex, x, y, points, color, duration ) 12 | local g_DebugOverlay = _G.g_DebugOverlay 13 | if ( g_DebugOverlay == nil ) then 14 | return 15 | end 16 | 17 | g_DebugOverlay:line( worldIndex, x, y, points, color, duration ) 18 | end 19 | 20 | function rectangle( worldIndex, x, y, width, height, color, duration ) 21 | local g_DebugOverlay = _G.g_DebugOverlay 22 | if ( g_DebugOverlay == nil ) then 23 | return 24 | end 25 | 26 | g_DebugOverlay:rectangle( 27 | worldIndex, 28 | x, 29 | y, 30 | width, 31 | height, 32 | color, 33 | duration 34 | ) 35 | end 36 | -------------------------------------------------------------------------------- /engine/client/graphics.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Extends the graphics module 4 | -- 5 | --==========================================================================-- 6 | 7 | require( "love.graphics" ) 8 | 9 | local newImage = love.graphics.newImage 10 | 11 | local function getHighResolutionVariant( filename ) 12 | local scale = love.window.getDPIScale() 13 | local extension = "." .. string.fileextension( filename ) 14 | local hrvariant = string.gsub( filename, extension, "" ) 15 | hrvariant = hrvariant .. "@" .. scale .. "x" .. extension 16 | if ( love.filesystem.getInfo( hrvariant ) ~= nil ) then 17 | return hrvariant 18 | end 19 | end 20 | 21 | function love.graphics.newImage( filename, ... ) 22 | if ( love.window.getDPIScale() > 1 ) then 23 | local variant = getHighResolutionVariant( filename ) 24 | if ( variant ) then 25 | filename = variant 26 | end 27 | end 28 | 29 | return newImage( filename, ... ) 30 | end 31 | -------------------------------------------------------------------------------- /engine/client/gui/autoloader.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: GUI autoloader 4 | -- 5 | --==========================================================================-- 6 | 7 | local error = error 8 | local ipairs = ipairs 9 | local pcall = pcall 10 | local rawget = rawget 11 | local require = require 12 | local setmetatable = setmetatable 13 | local string = string 14 | local _G = _G 15 | 16 | module( "gui" ) 17 | 18 | local metatable = {} 19 | local panelDirectories = { 20 | "game.client", 21 | "engine.client" 22 | } 23 | 24 | function metatable.__index( t, k ) 25 | -- Ignore private members. 26 | local privateMember = string.sub( k, 1, 1 ) == "_" 27 | if ( privateMember ) then 28 | return 29 | end 30 | 31 | -- Look in `/game/client/gui` and `/engine/client/gui` for 32 | -- panels not yet required and require them. 33 | -- 34 | -- Otherwise, return a standard Lua error. 35 | for _, module in ipairs( panelDirectories ) do 36 | local library = module .. ".gui." .. k 37 | local status, err = pcall( require, library ) 38 | if ( status == true ) then 39 | break 40 | end 41 | 42 | local message = "module '" .. library .. "' not found:" 43 | local notFound = string.find( err, message ) ~= 1 44 | if ( notFound ) then 45 | error( err, 2 ) 46 | end 47 | end 48 | 49 | -- Return pass-through. 50 | local v = rawget( t, k ) 51 | if ( v ~= nil ) then 52 | return v 53 | end 54 | end 55 | 56 | setmetatable( _M, metatable ) 57 | -------------------------------------------------------------------------------- /engine/client/gui/button.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Button class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.button" ( "gui.box" ) 8 | 9 | local button = gui.button 10 | 11 | button.canFocus = true 12 | 13 | function button:button( parent, name, text ) 14 | gui.box.box( self, parent, name ) 15 | self:setDisplay( "block" ) 16 | self:setPosition( "absolute" ) 17 | self:setBorderWidth( 1 ) 18 | self.width = 216 19 | self.height = 46 20 | self.text = gui.text( self, text ) 21 | self.disabled = false 22 | 23 | self:setScheme( "Default" ) 24 | end 25 | 26 | function button:draw() 27 | self:drawBackground() 28 | self:drawText() 29 | 30 | gui.box.draw( self ) 31 | end 32 | 33 | function button:drawBorder() 34 | local color = self:getScheme( "button.borderColor" ) 35 | 36 | if ( self:isDisabled() ) then 37 | color = self:getScheme( "button.disabled.borderColor" ) 38 | gui.box.drawBorder( self, color ) 39 | return 40 | end 41 | 42 | local mouseover = ( self.mouseover or self:isChildMousedOver() ) 43 | if ( self.mousedown and mouseover ) then 44 | color = self:getScheme( "button.mousedown.borderColor" ) 45 | elseif ( self.mousedown or mouseover or self.focus ) then 46 | color = self:getScheme( "button.mouseover.borderColor" ) 47 | end 48 | 49 | gui.box.drawBorder( self, color ) 50 | end 51 | 52 | function button:drawText() 53 | local color = self:getScheme( "button.textColor" ) 54 | 55 | if ( self:isDisabled() ) then 56 | color = self:getScheme( "button.disabled.textColor" ) 57 | end 58 | 59 | self.text:setColor( color ) 60 | end 61 | 62 | gui.accessor( button, "text" ) 63 | gui.accessor( button, "disabled", "is" ) 64 | 65 | function button:keypressed( key, scancode, isrepeat ) 66 | if ( not self.focus or self:isDisabled() ) then 67 | return 68 | end 69 | 70 | if ( key == "return" 71 | or key == "kpenter" 72 | or key == "space" ) then 73 | self:onClick() 74 | end 75 | end 76 | 77 | function button:mousepressed( x, y, button, istouch ) 78 | local mouseover = ( self.mouseover or self:isChildMousedOver() ) 79 | if ( mouseover and button == 1 ) then 80 | self.mousedown = true 81 | self:invalidate() 82 | end 83 | end 84 | 85 | function button:mousereleased( x, y, button, istouch ) 86 | local mouseover = ( self.mouseover or self:isChildMousedOver() ) 87 | if ( ( self.mousedown and mouseover ) and not self:isDisabled() ) then 88 | self:onClick() 89 | end 90 | 91 | if ( self.mousedown ) then 92 | self.mousedown = false 93 | self:invalidate() 94 | end 95 | end 96 | 97 | function button:onClick() 98 | end 99 | 100 | function button:setText( text ) 101 | self.text:set( text ) 102 | self:invalidate() 103 | end 104 | 105 | function button:getText() 106 | return self.text:get() 107 | end 108 | -------------------------------------------------------------------------------- /engine/client/gui/closebutton.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Close Button class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.closebutton" ( "gui.button" ) 8 | 9 | local closebutton = gui.closebutton 10 | 11 | closebutton.canFocus = false 12 | 13 | function closebutton:closebutton( parent, name ) 14 | gui.button.button( self, parent, name ) 15 | local padding = 36 16 | self:setPadding( padding ) 17 | self.width = 2 * padding + 8 - 1 18 | self.height = 2 * padding + 16 - 2 19 | self.icon = self:getScheme( "icon" ) 20 | end 21 | 22 | function closebutton:draw() 23 | local color = self:getScheme( "closebutton.iconColor" ) 24 | 25 | if ( self.mousedown and self.mouseover ) then 26 | color = self:getScheme( "closebutton.mousedown.iconColor" ) 27 | elseif ( self.mousedown or self.mouseover or self.focus ) then 28 | color = self:getScheme( "closebutton.mouseover.iconColor" ) 29 | end 30 | 31 | love.graphics.setColor( color ) 32 | 33 | local width = self:getWidth() 34 | local height = self:getHeight() 35 | local x = math.round( width / 2 - self.icon:getWidth() / 2 + 4 ) 36 | local y = math.round( height / 2 - self.icon:getHeight() / 2 ) 37 | love.graphics.draw( self.icon, x, y ) 38 | 39 | gui.panel.draw( self ) 40 | end 41 | 42 | function closebutton:onClick() 43 | local parent = self:getParent() 44 | parent:close() 45 | end 46 | -------------------------------------------------------------------------------- /engine/client/gui/commandbutton.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Command Button class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.commandbutton" ( "gui.button" ) 8 | 9 | local commandbutton = gui.commandbutton 10 | 11 | function commandbutton:commandbutton( parent, name, text ) 12 | gui.button.button( self, parent, name, text ) 13 | self.width = nil 14 | self.height = nil 15 | self:setDisplay( "inline-block" ) 16 | self:setPosition( "static" ) 17 | self:setPadding( 15, 18 ) 18 | self:setBorderWidth( 0 ) 19 | 20 | if ( text ) then 21 | parent:invalidateLayout() 22 | end 23 | end 24 | 25 | function commandbutton:drawBackground() 26 | local color = self:getScheme( "button.backgroundColor" ) 27 | local width = self:getWidth() 28 | local height = self:getHeight() 29 | 30 | if ( self:isDisabled() ) then 31 | color = self:getScheme( "button.disabled.backgroundColor" ) 32 | gui.panel.drawBackground( self, color ) 33 | return 34 | else 35 | gui.panel.drawBackground( self, color ) 36 | end 37 | 38 | local isFirstChild = self:isFirstChild() 39 | local x = isFirstChild and 1 or 0 40 | width = isFirstChild and width - 2 or width - 1 41 | height = height - 1 42 | 43 | local mouseover = ( self.mouseover or self:isChildMousedOver() ) 44 | if ( self.mousedown and mouseover ) then 45 | color = self:getScheme( "button.mousedown.backgroundColor" ) 46 | love.graphics.setColor( color ) 47 | love.graphics.rectangle( "fill", x, 1, width, height ) 48 | elseif ( self.mousedown or mouseover ) then 49 | color = self:getScheme( "button.mouseover.backgroundColor" ) 50 | love.graphics.setColor( color ) 51 | love.graphics.rectangle( "fill", x, 1, width, height ) 52 | end 53 | end 54 | 55 | function commandbutton:drawBorder() 56 | if ( self:isLastChild() ) then 57 | return 58 | end 59 | 60 | local color = self:getScheme( "commandbutton.borderColor" ) 61 | local width = self:getWidth() 62 | local height = self:getHeight() 63 | 64 | love.graphics.setColor( color ) 65 | love.graphics.setLineStyle( "rough" ) 66 | local lineWidth = 1 67 | love.graphics.setLineWidth( lineWidth ) 68 | love.graphics.line( 69 | width - lineWidth / 2, 0, -- Top-right 70 | width - lineWidth / 2, height -- Bottom-right 71 | ) 72 | end 73 | 74 | function commandbutton:isFirstChild() 75 | local children = self:getParent():getChildren() 76 | return self == children[ 1 ] 77 | end 78 | 79 | function commandbutton:isLastChild() 80 | local children = self:getParent():getChildren() 81 | return self == children[ #children ] 82 | end 83 | 84 | function commandbutton:setParent( panel ) 85 | gui.panel.setParent( self, panel ) 86 | panel:invalidate() 87 | end 88 | -------------------------------------------------------------------------------- /engine/client/gui/commandbuttongroup.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Command Button Group class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.commandbuttongroup" ( "gui.box" ) 8 | 9 | local commandbuttongroup = gui.commandbuttongroup 10 | 11 | function commandbuttongroup:commandbuttongroup( parent, name ) 12 | gui.box.box( self, parent, name ) 13 | self:setDisplay( "block" ) 14 | self:setPosition( "absolute" ) 15 | 16 | self:invalidateLayout() 17 | end 18 | 19 | function commandbuttongroup:draw() 20 | gui.box.draw( self ) 21 | 22 | local color = self:getScheme( "commandbuttongroup.borderColor" ) 23 | local width = self:getWidth() 24 | local height = self:getHeight() 25 | 26 | love.graphics.setColor( color ) 27 | love.graphics.setLineStyle( "rough" ) 28 | local lineWidth = 1 29 | love.graphics.setLineWidth( lineWidth ) 30 | love.graphics.line( 31 | lineWidth / 2, height, -- Bottom-left 32 | lineWidth / 2, lineWidth / 2, -- Top-left 33 | width - lineWidth / 2, lineWidth / 2, -- Top-right 34 | width - lineWidth / 2, height -- Bottom-right 35 | ) 36 | end 37 | 38 | function commandbuttongroup:invalidateLayout() 39 | local children = self:getChildren() 40 | local width = 0 41 | if ( children ) then 42 | for i, commandbutton in ipairs( children ) do 43 | commandbutton:setX( width ) 44 | width = width + commandbutton:getWidth() 45 | end 46 | end 47 | self:setWidth( width ) 48 | 49 | local parent = self:getParent() 50 | local margin = typeof( parent, "tabbedframe" ) and 24 or 36 51 | self:setPos( 52 | parent:getWidth() - self:getWidth() - margin, 53 | parent:getHeight() - self:getHeight() 54 | ) 55 | gui.panel.invalidateLayout( self ) 56 | end 57 | -------------------------------------------------------------------------------- /engine/client/gui/console/textbox.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Console Text Box class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.console.textbox" ( "gui.textbox" ) 8 | 9 | local textbox = gui.console.textbox 10 | 11 | function textbox:textbox( parent, name ) 12 | gui.textbox.textbox( self, parent, name, "" ) 13 | 14 | self:setEditable( false ) 15 | self:setMultiline( true ) 16 | self:setScheme( "Console" ) 17 | end 18 | 19 | function textbox:draw() 20 | if ( self:getHeight() == 1 ) then 21 | return 22 | end 23 | 24 | gui.panel.drawBackground( self, self:getScheme( "textbox.backgroundColor" ) ) 25 | self:drawText() 26 | self:drawCursor() 27 | 28 | gui.panel.draw( self ) 29 | 30 | self:drawBorder() 31 | end 32 | 33 | function textbox:invalidateLayout() 34 | local parent = self:getParent() 35 | local margin = 36 36 | local titleBarHeight = 86 37 | local textboxHeight = 46 38 | local marginBottom = 9 39 | self:setWidth( parent:getWidth() - 2 * margin ) 40 | self:setHeight( 41 | parent:getHeight() - 42 | titleBarHeight - 43 | textboxHeight - 44 | marginBottom - 45 | margin 46 | ) 47 | 48 | gui.panel.invalidateLayout( self ) 49 | end 50 | -------------------------------------------------------------------------------- /engine/client/gui/console/textboxautocompleteitemgroup.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Console Text Box Autocomplete Item Group class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.console.textboxautocompleteitemgroup" ( "gui.textboxautocompleteitemgroup" ) 8 | 9 | local textboxautocompleteitemgroup = gui.console.textboxautocompleteitemgroup 10 | 11 | function textboxautocompleteitemgroup:textboxautocompleteitemgroup( parent, name ) 12 | gui.textboxautocompleteitemgroup.textboxautocompleteitemgroup( self, parent, name ) 13 | end 14 | 15 | function textboxautocompleteitemgroup:invalidateLayout() 16 | local itemWidth = 0 17 | local font = self:getScheme( "font" ) 18 | local maxWidth = 0 19 | local listItems = self:getItems() 20 | if ( listItems ) then 21 | local y = 1 22 | local padding = 18 23 | for _, listItem in ipairs( listItems ) do 24 | listItem:setX( 1 ) 25 | listItem:setY( y ) 26 | 27 | itemWidth = font:getWidth( listItem:getText() ) + 2 * padding 28 | if ( itemWidth > maxWidth ) then 29 | maxWidth = itemWidth 30 | end 31 | y = y + listItem:getHeight() 32 | end 33 | 34 | self:setWidth( maxWidth + 2 ) 35 | for _, listItem in ipairs( listItems ) do 36 | listItem:setWidth( maxWidth ) 37 | end 38 | end 39 | 40 | self:updatePos() 41 | end 42 | -------------------------------------------------------------------------------- /engine/client/gui/dropdownlistitem.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Drop-Down List Item class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.dropdownlistitem" ( "gui.radiobutton" ) 8 | 9 | local dropdownlistitem = gui.dropdownlistitem 10 | 11 | function dropdownlistitem:dropdownlistitem( parent, name, text ) 12 | gui.radiobutton.radiobutton( self, nil, name, text ) 13 | self:setPadding( 15, 18, 14 ) 14 | self:setDisplay( "block" ) 15 | self:setPosition( "static" ) 16 | 17 | parent:addItem( self ) 18 | 19 | local parent = self:getParent() 20 | self.width = nil 21 | self.height = nil 22 | self.text:set( text ) 23 | 24 | self:invalidateLayout() 25 | end 26 | 27 | function dropdownlistitem:draw() 28 | self:drawBackground() 29 | self:drawText() 30 | 31 | gui.box.draw( self ) 32 | end 33 | 34 | function dropdownlistitem:drawBackground() 35 | local color = self:getScheme( "dropdownlistitem.backgroundColor" ) 36 | local width = self:getWidth() 37 | local height = self:getHeight() 38 | 39 | if ( self:isSelected() ) then 40 | color = self:getScheme( "dropdownlistitem.selected.backgroundColor" ) 41 | elseif ( ( self.mouseover or self:isChildMousedOver() ) ) then 42 | color = self:getScheme( "dropdownlistitem.mouseover.backgroundColor" ) 43 | end 44 | 45 | love.graphics.setColor( color ) 46 | love.graphics.rectangle( "fill", 0, 0, width, height ) 47 | end 48 | 49 | function dropdownlistitem:drawText() 50 | local color = self:getScheme( "button.textColor" ) 51 | 52 | if ( self:isDisabled() ) then 53 | color = self:getScheme( "button.disabled.textColor" ) 54 | elseif ( self:isSelected() ) then 55 | color = self:getScheme( "dropdownlistitem.selected.textColor" ) 56 | elseif ( ( self.mouseover or self:isChildMousedOver() ) ) then 57 | color = self:getScheme( "dropdownlistitem.mouseover.textColor" ) 58 | end 59 | 60 | self.text:setColor( color ) 61 | end 62 | 63 | local function getParentFrame( self ) 64 | local panel = self:getParent():getDropDownList() 65 | while ( panel ~= nil ) do 66 | panel = panel:getParent() 67 | if ( typeof( panel, "frame" ) ) then 68 | return panel 69 | end 70 | end 71 | end 72 | 73 | function dropdownlistitem:mousepressed( x, y, button, istouch ) 74 | if ( ( self.mouseover or self:isChildMousedOver() ) and button == 1 ) then 75 | self.mousedown = true 76 | end 77 | 78 | local parentFrame = getParentFrame( self ) 79 | if ( parentFrame ) then 80 | parentFrame:setFocusedFrame( true ) 81 | end 82 | end 83 | 84 | function dropdownlistitem:invalidateLayout() 85 | local parent = self:getParent() 86 | local t, r, b, l = parent:getBorderWidth() 87 | self:setWidth( parent:getWidth() - r - l ) 88 | gui.panel.invalidateLayout( self ) 89 | end 90 | -------------------------------------------------------------------------------- /engine/client/gui/dropdownlistitemgroup.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Drop-Down List Item Group class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.dropdownlistitemgroup" ( "gui.radiobuttongroup" ) 8 | 9 | local dropdownlistitemgroup = gui.dropdownlistitemgroup 10 | 11 | function dropdownlistitemgroup:dropdownlistitemgroup( parent, name ) 12 | gui.radiobuttongroup.radiobuttongroup( self, nil, name ) 13 | self:setParent( parent:getRootPanel() ) 14 | self.height = nil 15 | self:setBorderWidth( 1 ) 16 | self:setBorderColor( self:getScheme( "dropdownlistitem.borderColor" ) ) 17 | self:setDisplay( "block" ) 18 | self:setPosition( "absolute" ) 19 | self.width = parent:getWidth() 20 | self:setUseFullscreenCanvas( true ) 21 | self.dropDownList = parent 22 | end 23 | 24 | function dropdownlistitemgroup:addItem( item, default ) 25 | item:setParent( self ) 26 | gui.radiobuttongroup.addItem( self, item ) 27 | 28 | if ( default or #self:getItems() == 1 ) then 29 | item:setDefault( true ) 30 | end 31 | 32 | self:invalidateLayout() 33 | end 34 | 35 | function dropdownlistitemgroup:draw() 36 | if ( self:getItems() == nil ) then 37 | return 38 | end 39 | 40 | gui.box.draw( self ) 41 | end 42 | 43 | accessor( dropdownlistitemgroup, "dropDownList" ) 44 | 45 | function dropdownlistitemgroup:invalidateLayout() 46 | self:updatePos() 47 | self:setWidth( self:getDropDownList():getWidth() ) 48 | gui.panel.invalidateLayout( self ) 49 | end 50 | 51 | function dropdownlistitemgroup:isVisible() 52 | local dropDownList = self:getDropDownList() 53 | return dropDownList:isVisible() and dropDownList:isActive() 54 | end 55 | 56 | function dropdownlistitemgroup:mousepressed( x, y, button, istouch ) 57 | if ( button == 1 ) then 58 | local dropDownList = self:getDropDownList() 59 | if ( dropDownList ~= gui._topPanel and 60 | ( not ( self.mouseover or self:isChildMousedOver() ) ) ) then 61 | dropDownList:setActive( false ) 62 | end 63 | end 64 | 65 | return gui.panel.mousepressed( self, x, y, button, istouch ) 66 | end 67 | 68 | function dropdownlistitemgroup:onValueChanged( oldValue, newValue ) 69 | local dropDownList = self:getDropDownList() 70 | dropDownList:setActive( false ) 71 | dropDownList:onValueChanged( oldValue, newValue ) 72 | end 73 | 74 | function dropdownlistitemgroup:updatePos() 75 | local dropDownList = self:getDropDownList() 76 | if ( dropDownList == nil ) then 77 | return 78 | end 79 | 80 | local x, y = dropDownList:localToScreen() 81 | y = y + dropDownList:getHeight() 82 | 83 | local windowPadding = 4 84 | local overflow = y + self:getHeight() + windowPadding 85 | if ( overflow > love.graphics.getHeight() ) then 86 | overflow = overflow - love.graphics.getHeight() 87 | y = y - overflow 88 | end 89 | 90 | if ( y < windowPadding ) then 91 | y = windowPadding 92 | end 93 | 94 | self:setPos( x, y ) 95 | end 96 | -------------------------------------------------------------------------------- /engine/client/gui/framerate.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Frame Rate class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.framerate" ( "gui.label" ) 8 | 9 | local framerate = gui.framerate 10 | 11 | function framerate:framerate( parent, name ) 12 | gui.label.label( self, parent, name, text ) 13 | 14 | self:setScheme( "Console" ) 15 | self.font = self:getScheme( "font" ) 16 | self.height = self.font:getHeight() 17 | 18 | self:setScheme( "Default" ) 19 | self:setTextAlign( "right" ) 20 | self:invalidateLayout() 21 | end 22 | 23 | function framerate:update( dt ) 24 | -- HACKHACK: Fade this out for readability. 25 | if ( ( g_MainMenu and not g_MainMenu:isVisible() ) and 26 | ( g_GameMenu and g_GameMenu:isVisible() ) ) then 27 | self:setOpacity( 1 - g_GameMenu:getOpacity() ) 28 | else 29 | self:setOpacity( 1 ) 30 | end 31 | 32 | local framerate = self:getFramerate() 33 | local text = self:getText() 34 | if ( text ~= framerate ) then 35 | self:setText( self:getFramerate() ) 36 | self:invalidate() 37 | end 38 | end 39 | 40 | function framerate:getFramerate() 41 | local fps = love.timer.getFPS() .. " FPS" 42 | local ms = 1000 * love.timer.getAverageDelta() 43 | ms = string.format( "%.3f", ms ) .. " ms" 44 | return fps .. " / " .. ms 45 | end 46 | 47 | function framerate:invalidateLayout() 48 | local parent = self:getParent() 49 | local margin = gui.scale( 96 ) 50 | local width = self:getWidth() 51 | local height = self:getHeight() 52 | local x = parent:getWidth() - margin - width 53 | local y = parent:getHeight() - margin - height 54 | self:setPos( x, y ) 55 | 56 | gui.panel.invalidateLayout( self ) 57 | end 58 | -------------------------------------------------------------------------------- /engine/client/gui/frametab.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Frame Tab class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.frametab" ( "gui.radiobutton" ) 8 | 9 | local frametab = gui.frametab 10 | 11 | function frametab:frametab( parent, name, text ) 12 | gui.radiobutton.radiobutton( self, parent, name, text ) 13 | self:setDisplay( "inline-block" ) 14 | self:setPadding( 22 ) 15 | self.text:set( text ) 16 | self.width = nil 17 | self.height = nil 18 | end 19 | 20 | function frametab:draw() 21 | self:drawBackground() 22 | self:drawText() 23 | gui.box.draw( self ) 24 | end 25 | 26 | function frametab:drawBackground() 27 | local color = self:getScheme( "frametab.backgroundColor" ) 28 | local mouseover = self.mouseover or self:isChildMousedOver() 29 | local width = self:getWidth() 30 | local height = self:getHeight() 31 | 32 | 33 | if ( self:isSelected() ) then 34 | color = self:getScheme( "frametab.selected.backgroundColor" ) 35 | elseif ( mouseover ) then 36 | gui.panel.drawBackground( self, color ) 37 | color = self:getScheme( "frametab.mouseover.backgroundColor" ) 38 | end 39 | 40 | love.graphics.setColor( color ) 41 | 42 | local selected = mouseover or self:isSelected() 43 | mouseover = mouseover and not self:isSelected() 44 | love.graphics.rectangle( 45 | "fill", 46 | 0, 47 | 0, 48 | width - ( selected and 1 or 0 ), 49 | height - ( mouseover and 1 or 0 ) 50 | ) 51 | 52 | local lineWidth = 1 53 | if ( selected ) then 54 | love.graphics.setColor( self:getScheme( "frametab.backgroundColor" ) ) 55 | love.graphics.setLineStyle( "rough" ) 56 | love.graphics.setLineWidth( lineWidth ) 57 | love.graphics.line( 58 | width - lineWidth / 2, 0, -- Top-left 59 | width - lineWidth / 2, height -- Bottom-left 60 | ) 61 | end 62 | 63 | selected = self:isSelected() 64 | love.graphics.setColor( self:getScheme( "frametab.borderColor" ) ) 65 | love.graphics.setLineStyle( "rough" ) 66 | love.graphics.setLineWidth( lineWidth ) 67 | love.graphics.line( 68 | width - lineWidth / 2, 0, 69 | width - lineWidth / 2, height - ( selected and 0 or 1 ) 70 | ) 71 | 72 | if ( not selected ) then 73 | love.graphics.line( 74 | 0, height - lineWidth / 2, -- Top-right 75 | width, height - lineWidth / 2 -- Bottom-right 76 | ) 77 | end 78 | end 79 | 80 | function frametab:mousepressed( x, y, button, istouch ) 81 | if ( ( self.mouseover or self:isChildMousedOver() ) and button == 1 ) then 82 | self.mousedown = true 83 | 84 | if ( not self:isDisabled() ) then 85 | local frametabgroup = self:getGroup() 86 | if ( frametabgroup ) then 87 | frametabgroup:setSelectedId( self.id ) 88 | self:onClick() 89 | end 90 | end 91 | end 92 | 93 | self:invalidate() 94 | end 95 | 96 | function frametab:mousereleased( x, y, button, istouch ) 97 | self.mousedown = false 98 | self:invalidate() 99 | end 100 | -------------------------------------------------------------------------------- /engine/client/gui/frametabgroup.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Frame Tab Group class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.frametabgroup" ( "gui.radiobuttongroup" ) 8 | 9 | local frametabgroup = gui.frametabgroup 10 | 11 | function frametabgroup:frametabgroup( parent, name ) 12 | gui.radiobuttongroup.radiobuttongroup( self, parent, name ) 13 | self.height = 61 14 | self:setScheme( "Default" ) 15 | end 16 | 17 | function frametabgroup:addTab( tabName, default ) 18 | local frametab = gui.frametab( self, tabName .. " Frame Tab", tabName ) 19 | self:addItem( frametab ) 20 | local numItems = #self:getItems() 21 | frametab:setValue( numItems ) 22 | 23 | if ( default or numItems == 1 ) then 24 | frametab:setDefault( true ) 25 | end 26 | end 27 | 28 | function frametabgroup:addItem( tab ) 29 | gui.radiobuttongroup.addItem( self, tab ) 30 | self:invalidateLayout() 31 | end 32 | 33 | function frametabgroup:draw() 34 | love.graphics.setColor( self:getScheme( "frametab.borderColor" ) ) 35 | love.graphics.setLineStyle( "rough" ) 36 | local lineWidth = 1 37 | love.graphics.setLineWidth( lineWidth ) 38 | love.graphics.line( 39 | lineWidth / 2, 0, -- Top-left 40 | lineWidth / 2, self:getHeight() -- Bottom-left 41 | ) 42 | 43 | gui.panel.draw( self ) 44 | end 45 | 46 | function frametabgroup:invalidateLayout() 47 | local tabs = self:getItems() 48 | if ( tabs ) then 49 | local x = 1 50 | for _, tab in ipairs( tabs ) do 51 | tab:setX( x ) 52 | x = x + tab:getWidth() 53 | end 54 | self:setWidth( x ) 55 | end 56 | 57 | gui.panel.invalidateLayout( self ) 58 | end 59 | 60 | function frametabgroup:onValueChanged( oldValue, newValue ) 61 | local tabPanels = self:getParent():getTabPanels() 62 | tabPanels:setSelectedChild( newValue ) 63 | end 64 | -------------------------------------------------------------------------------- /engine/client/gui/frametabpanel.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Frame Tab Panel class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.frametabpanel" ( "gui.box" ) 8 | 9 | local frametabpanel = gui.frametabpanel 10 | 11 | function frametabpanel:frametabpanel( parent, name ) 12 | gui.box.box( self, parent, name ) 13 | self:setDisplay( "block" ) 14 | self:setPosition( "absolute" ) 15 | end 16 | 17 | function frametabpanel:draw() 18 | gui.panel.drawBackground( self, self:getScheme( "frame.backgroundColor" ) ) 19 | gui.box.draw( self ) 20 | end 21 | 22 | function frametabpanel:invalidateLayout() 23 | self:setDimensions( self:getParent():getDimensions() ) 24 | gui.panel.invalidateLayout( self ) 25 | end 26 | 27 | local function getParentFrame( self ) 28 | local panel = self 29 | while ( panel ~= nil ) do 30 | panel = panel:getParent() 31 | if ( typeof( panel, "frame" ) ) then 32 | return panel 33 | end 34 | end 35 | end 36 | 37 | function frametabpanel:keypressed( key, scancode, isrepeat ) 38 | local parentFrame = getParentFrame( self ) 39 | local parentFocus = parentFrame and parentFrame.focus 40 | if ( key == "tab" and ( self.focus or parentFocus ) ) then 41 | gui.frame.moveFocus( self ) 42 | end 43 | 44 | return gui.panel.keypressed( self, key, scancode, isrepeat ) 45 | end 46 | -------------------------------------------------------------------------------- /engine/client/gui/frametabpanels.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Frame Tab Panels class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.frametabpanels" ( "gui.box" ) 8 | 9 | local frametabpanels = gui.frametabpanels 10 | 11 | function frametabpanels:frametabpanels( parent, name, text ) 12 | gui.box.box( self, parent, name ) 13 | self:setDisplay( "block" ) 14 | self:setPosition( "absolute" ) 15 | self.width = parent:getWidth() 16 | self.height = parent:getHeight() - 62 17 | end 18 | 19 | function frametabpanels:addPanel( frametabpanel, default ) 20 | local panel = frametabpanel( self ) 21 | panel:setDimensions( self:getDimensions() ) 22 | 23 | if ( not default and #self:getChildren() ~= 1 ) then 24 | panel:setVisible( false ) 25 | end 26 | 27 | return panel 28 | end 29 | 30 | function frametabpanels:setSelectedChild( i ) 31 | for j, v in ipairs( self:getChildren() ) do 32 | v:setVisible( i == j ) 33 | v:invalidate() 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /engine/client/gui/hudframe.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Frame HUD 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.hudframe" ( "gui.frame" ) 8 | 9 | local hudframe = gui.hudframe 10 | 11 | function hudframe:hudframe( parent, name, title ) 12 | gui.frame.frame( self, parent, name, title ) 13 | 14 | self:setResizable( false ) 15 | self:setMovable( false ) 16 | 17 | if ( self.closeButton ) then 18 | self.closeButton:remove() 19 | self.closeButton = nil 20 | end 21 | 22 | self:setUseFullscreenCanvas( false ) 23 | self:invalidateLayout() 24 | end 25 | 26 | local HUDFRAME_ANIM_TIME = 0.2 27 | 28 | function hudframe:activate() 29 | if ( not self:isVisible() ) then 30 | self:setOpacity( 0 ) 31 | self:animate( { 32 | opacity = 1 33 | }, HUDFRAME_ANIM_TIME, "easeOutQuint" ) 34 | end 35 | 36 | self:moveToFront() 37 | self:setVisible( true ) 38 | end 39 | 40 | function hudframe:close() 41 | if ( self.closing ) then 42 | return 43 | end 44 | 45 | self.closing = true 46 | 47 | self:animate( { 48 | opacity = 0, 49 | }, HUDFRAME_ANIM_TIME, "easeOutQuint", function() 50 | self:setVisible( false ) 51 | self:setOpacity( 1 ) 52 | 53 | self.closing = nil 54 | end ) 55 | end 56 | 57 | function hudframe:draw() 58 | self:drawTranslucency() 59 | gui.frame.draw( self ) 60 | end 61 | 62 | function hudframe:drawBackground() 63 | if ( gui._translucencyCanvas == nil ) then 64 | gui.panel.drawBackground( self, self:getScheme( 65 | "frame.backgroundColor" 66 | ) ) 67 | return 68 | end 69 | 70 | gui.box.drawBackground( self ) 71 | end 72 | 73 | function hudframe:drawTitle() 74 | local property = "frame.titleTextColor" 75 | love.graphics.setColor( self:getScheme( property ) ) 76 | local font = self:getScheme( "titleFont" ) 77 | love.graphics.setFont( font ) 78 | local x = math.round( 36 ) 79 | local y = math.round( x - 4 ) 80 | love.graphics.print( self:getTitle(), x, y ) 81 | end 82 | 83 | function hudframe:update( dt ) 84 | if ( gui._translucencyCanvas and self:isVisible() ) then 85 | self:invalidate() 86 | end 87 | 88 | gui.frame.update( self, dt ) 89 | end 90 | -------------------------------------------------------------------------------- /engine/client/gui/hudprofiler.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Profiler HUD 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.hudprofiler" ( "gui.hudframe" ) 8 | 9 | local hudprofiler = gui.hudprofiler 10 | 11 | function hudprofiler:hudprofiler( parent ) 12 | local name = "HUD Profiler" 13 | gui.hudframe.hudframe( self, parent, name, name ) 14 | self.width = 320 -- - 31 15 | self.height = 432 16 | self:setBorderColor( self:getScheme( "borderColor" ) ) 17 | 18 | local budgets = profile._stack 19 | for i, budget in ipairs( budgets ) do 20 | local box = gui.box( self, budget.name .. " Budget Info" ) 21 | box:setDisplay( "block" ) 22 | box:setMargin( 16, 0 ) 23 | local text = gui.text( box, budget.name ) 24 | text:setColor( self:getScheme( "textColor" ) ) 25 | gui.progressbar( box ) 26 | end 27 | 28 | self:invalidateLayout() 29 | end 30 | 31 | function hudprofiler:draw() 32 | self:drawTranslucency() 33 | self:drawBackground() 34 | 35 | gui.box.draw( self ) 36 | 37 | self:drawTitle() 38 | -- self:drawBorder( self:getScheme( "borderColor" ) ) 39 | 40 | if ( convar.getConvar( "gui_draw_frame_focus" ):getBoolean() and 41 | self.focus ) then 42 | self:drawSelection() 43 | end 44 | end 45 | 46 | function hudprofiler:getTitle() 47 | return "Profiler" 48 | end 49 | 50 | function hudprofiler:invalidateLayout() 51 | local x = love.graphics.getWidth() - self:getWidth() - 18 52 | local y = love.graphics.getHeight() - self:getHeight() - 18 53 | self:setPos( x, y ) 54 | gui.frame.invalidateLayout( self ) 55 | end 56 | 57 | concommand( "+profiler", "Opens the profiler", function() 58 | local visible = _G.g_Profiler:isVisible() 59 | if ( not visible ) then 60 | _G.g_Profiler:activate() 61 | end 62 | end, { "game" } ) 63 | 64 | concommand( "-profiler", "Closes the profiler", function() 65 | local visible = _G.g_Profiler:isVisible() 66 | if ( visible ) then 67 | _G.g_Profiler:close() 68 | end 69 | end, { "game" } ) 70 | 71 | local function onReloadScript() 72 | local profiler = g_Profiler 73 | if ( profiler == nil ) then 74 | return 75 | end 76 | 77 | local visible = profiler:isVisible() 78 | profiler:remove() 79 | profiler = gui.hudprofiler( g_Viewport ) 80 | g_Profiler = profiler 81 | if ( visible ) then 82 | profiler:activate() 83 | end 84 | end 85 | 86 | onReloadScript() 87 | -------------------------------------------------------------------------------- /engine/client/gui/hudvoice.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Voice HUD 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.hudvoice" ( "gui.box" ) 8 | 9 | local hudvoice = gui.hudvoice 10 | 11 | function hudvoice:hudvoice( parent, name ) 12 | gui.box.box( self, parent, name ) 13 | end 14 | 15 | function hudvoice:draw() 16 | gui.box.draw( self ) 17 | end 18 | -------------------------------------------------------------------------------- /engine/client/gui/imagepanel.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Image class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.imagepanel" ( "gui.box" ) 8 | 9 | local imagepanel = gui.imagepanel 10 | 11 | function imagepanel:imagepanel( parent, name, image ) 12 | gui.box.box( self, parent, name ) 13 | self:setDisplay( "block" ) 14 | self:setPosition( "absolute" ) 15 | 16 | self.imageDatum = nil 17 | self.imageQuad = nil 18 | self:setImage( image ) 19 | end 20 | 21 | function imagepanel:draw() 22 | local image = self:getImage() 23 | if ( image ) then 24 | gui.panel._maskedPanel = self 25 | love.graphics.stencil( gui.panel.drawMask ) 26 | love.graphics.setStencilTest( "greater", 0 ) 27 | love.graphics.setColor( self:getColor() ) 28 | love.graphics.draw( image, self:getQuad() ) 29 | love.graphics.setStencilTest() 30 | else 31 | self:drawMissingImage() 32 | end 33 | end 34 | 35 | function imagepanel:drawMissingImage() 36 | love.graphics.setColor( color( color.red, 0.42 * 255 ) ) 37 | love.graphics.setLineStyle( "rough" ) 38 | local lineWidth = 1 39 | local width = self:getWidth() 40 | local height = self:getHeight() 41 | love.graphics.setLineWidth( lineWidth ) 42 | love.graphics.line( 43 | width - lineWidth / 2, 0, -- Top-right 44 | width - lineWidth / 2, height - lineWidth / 2, -- Bottom-right 45 | 0, height - lineWidth / 2 -- Bottom-left 46 | ) 47 | end 48 | 49 | gui.accessor( imagepanel, "quad", nil, "imageQuad" ) 50 | gui.accessor( imagepanel, "image", nil, "imageDatum" ) 51 | 52 | function imagepanel:setImage( image ) 53 | if ( type( image ) == "image" ) then 54 | self.imageDatum = image 55 | elseif ( image ~= nil and love.filesystem.getInfo( image ) ~= nil ) then 56 | self.imageDatum = love.graphics.newImage( image ) 57 | else 58 | self.imageDatum = nil 59 | end 60 | 61 | self:updateQuad() 62 | end 63 | 64 | function imagepanel:setWidth( width ) 65 | gui.panel.setWidth( self, width ) 66 | self:updateQuad() 67 | end 68 | 69 | function imagepanel:setHeight( height ) 70 | gui.panel.setHeight( self, height ) 71 | self:updateQuad() 72 | end 73 | 74 | function imagepanel:updateQuad() 75 | local missingImage = self:getImage() == nil 76 | if ( missingImage ) then 77 | return 78 | end 79 | 80 | local w = self:getWidth() - ( missingImage and 1 or 0 ) 81 | local h = self:getHeight() - ( missingImage and 1 or 0 ) 82 | local sw = self.imageDatum:getWidth() 83 | local sh = self.imageDatum:getHeight() 84 | if ( self.imageQuad == nil ) then 85 | self.imageQuad = love.graphics.newQuad( 0, 0, w, h, sw, sh ) 86 | else 87 | self.imageQuad:setViewport( 0, 0, w, h ) 88 | end 89 | end 90 | -------------------------------------------------------------------------------- /engine/client/gui/init.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: GUI interface 4 | -- 5 | --==========================================================================-- 6 | 7 | require( "engine.client.gui.scheme" ) 8 | 9 | local love = love 10 | local math = math 11 | local require = require 12 | local string = string 13 | local type = type 14 | local typerror = typerror 15 | local _G = _G 16 | 17 | module( "gui" ) 18 | 19 | require( "engine.client.gui.autoloader" ) 20 | require( "engine.client.gui.handlers" ) 21 | 22 | function accessor( class, member, verb, key, default ) 23 | if ( type( class ) ~= "table" ) then 24 | typerror( 1, "table", class ) 25 | end 26 | 27 | class[ "set" .. string.capitalize( member ) ] = function( self, value ) 28 | self[ key or member ] = value 29 | self:invalidate() 30 | end 31 | 32 | class[ ( verb or "get" ) .. string.capitalize( member ) ] = function( self ) 33 | return self[ key or member ] or default 34 | end 35 | end 36 | 37 | function invalidateTree() 38 | _rootPanel:invalidateLayout() 39 | _rootPanel:invalidateCanvas() 40 | 41 | if ( _viewportCanvas ) then 42 | _viewportCanvas:remove() 43 | end 44 | 45 | _viewportCanvas = nil 46 | _translucencyCanvas = nil 47 | end 48 | 49 | function preDrawWorld() 50 | _rootPanel:preDrawWorld() 51 | end 52 | 53 | function scale( n ) 54 | return math.round( n * ( love.graphics.getHeight() / 1080 ) ) 55 | end 56 | 57 | function setFocusedPanel( panel, focus ) 58 | if ( _focusedPanel ) then 59 | _focusedPanel.focus = nil 60 | 61 | if ( _focusedPanel.onLostFocus ) then 62 | _focusedPanel:onLostFocus() 63 | end 64 | 65 | if ( _focusedPanel ) then 66 | _focusedPanel:invalidate() 67 | end 68 | end 69 | 70 | if ( focus ) then 71 | _focusedPanel = panel 72 | if ( panel and panel.canFocus ) then 73 | panel.focus = focus 74 | 75 | if ( _focusedPanel.onFocus ) then 76 | _focusedPanel:onFocus() 77 | end 78 | 79 | panel:invalidate() 80 | end 81 | else 82 | _focusedPanel = nil 83 | if ( panel ) then 84 | panel.focus = nil 85 | panel:invalidate() 86 | end 87 | end 88 | end 89 | -------------------------------------------------------------------------------- /engine/client/gui/label.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Label class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.label" ( "gui.box" ) 8 | 9 | local label = gui.label 10 | 11 | function label:label( parent, name, text ) 12 | gui.box.box( self, parent, name ) 13 | self:setPosition( "absolute" ) 14 | 15 | self.font = self:getScheme( "font" ) 16 | self.width = 216 17 | self.height = self.font:getHeight() 18 | self.text = text or "Label" 19 | end 20 | 21 | function label:draw() 22 | love.graphics.setColor( self:getScheme( "label.textColor" ) ) 23 | 24 | local font = self:getFont() 25 | love.graphics.setFont( font ) 26 | 27 | local text = self:getText() 28 | local limit = self:getWidth() 29 | local align = self:getTextAlign() 30 | love.graphics.printf( text, 0, 0, limit, align ) 31 | 32 | gui.box.draw( self ) 33 | end 34 | 35 | gui.accessor( label, "font" ) 36 | gui.accessor( label, "text" ) 37 | gui.accessor( label, "textAlign" ) 38 | -------------------------------------------------------------------------------- /engine/client/gui/netgraph.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Net Graph class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.netgraph" ( "gui.box" ) 8 | 9 | local netgraph = gui.netgraph 10 | 11 | function netgraph:netgraph( parent, name ) 12 | gui.box.box( self, parent, name ) 13 | self:setDisplay( "block" ) 14 | self:setPosition( "absolute" ) 15 | 16 | self:setScheme( "Console" ) 17 | self.font = self:getScheme( "font" ) 18 | self.width = 216 19 | self.height = 3 * self.font:getHeight() 20 | self:invalidateLayout() 21 | end 22 | 23 | function netgraph:update( dt ) 24 | -- HACKHACK: Fade this out for readability. 25 | if ( ( g_MainMenu and not g_MainMenu:isVisible() ) and 26 | ( g_GameMenu and g_GameMenu:isVisible() ) ) then 27 | self:setOpacity( 1 - g_GameMenu:getOpacity() ) 28 | else 29 | self:setOpacity( 1 ) 30 | end 31 | 32 | self:invalidate() 33 | end 34 | 35 | function netgraph:draw() 36 | self:drawSentReceived() 37 | 38 | gui.box.draw( self ) 39 | end 40 | 41 | function netgraph:drawSentReceived() 42 | if ( not engine.client.isInGame() ) then 43 | return 44 | end 45 | 46 | self:setScheme( "Default" ) 47 | love.graphics.setColor( self:getScheme( "label.textColor" ) ) 48 | 49 | local font = self:getFont() 50 | love.graphics.setFont( font ) 51 | local text = "" 52 | 53 | local network = engine.client.network 54 | 55 | if ( _SERVER ) then 56 | network = engine.server.network 57 | end 58 | 59 | -- Ping 60 | if ( not _SERVER ) then 61 | local ping = network._server:round_trip_time() 62 | text = text .. "Ping: " .. ping .. " ms\n" 63 | else 64 | text = text .. "\n" 65 | end 66 | 67 | -- Send 68 | local sent = network.getAverageSentData() or 0 69 | local rate = "B/s" 70 | if ( sent >= 1024 ) then 71 | sent = sent / 1024 72 | sent = string.format( "%.2f", sent ) 73 | rate = "kB/s" 74 | end 75 | text = text .. "Data sent/sec: " .. sent .. " " .. rate .. "\n" 76 | 77 | -- Receive 78 | local received = network.getAverageReceivedData() or 0 79 | local rate = "B/s" 80 | if ( received >= 1024 ) then 81 | received = received / 1024 82 | received = string.format( "%.2f", received ) 83 | rate = "kB/s" 84 | end 85 | 86 | text = text .. "Data received/sec: " .. received .. " " .. rate 87 | local limit = self:getWidth() 88 | local align = "right" 89 | love.graphics.printf( text, 0, 0, limit, align ) 90 | end 91 | 92 | accessor( netgraph, "font" ) 93 | 94 | function netgraph:invalidateLayout() 95 | local parent = self:getParent() 96 | local margin = gui.scale( 96 ) 97 | local width = self:getWidth() 98 | local height = self:getHeight() 99 | local x = parent:getWidth() - margin - width 100 | local y = parent:getHeight() - margin - height - self.font:getHeight() 101 | self:setPos( x, y ) 102 | 103 | gui.panel.invalidateLayout( self ) 104 | end 105 | -------------------------------------------------------------------------------- /engine/client/gui/optionsmenu/audiooptionspanel.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Audio Options Panel class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.audiooptionspanel" ( "gui.frametabpanel" ) 8 | 9 | local audiooptionspanel = gui.audiooptionspanel 10 | 11 | function audiooptionspanel:audiooptionspanel( parent, name ) 12 | name = name or "Audio Options Panel" 13 | gui.frametabpanel.frametabpanel( self, parent, name ) 14 | local options = {} 15 | self.options = options 16 | local c = config.getConfig() 17 | 18 | local name = "Master Volume" 19 | local label = gui.label( self, name, name ) 20 | local margin = 36 21 | local x = margin 22 | local y = margin 23 | label:setPos( x, y ) 24 | label:setFont( self:getScheme( "fontBold" ) ) 25 | 26 | name = "Master Volume Slider" 27 | local masterVolume = gui.slider( self, name ) 28 | self.masterVolume = masterVolume 29 | options.masterVolume = c.sound.volume 30 | masterVolume:setMax( 1 ) 31 | masterVolume:setValue( c.sound.volume ) 32 | masterVolume.onValueChanged = function( slider, oldValue, newValue ) 33 | options.masterVolume = newValue 34 | c.sound.volume = newValue 35 | end 36 | local marginBottom = 9 37 | y = y + label:getHeight() + marginBottom 38 | masterVolume:setPos( x, y ) 39 | 40 | name = "Play Sound in Desktop" 41 | local desktopSound = gui.checkbox( self, name, name ) 42 | self.desktopSound = desktopSound 43 | options.desktopSound = c.sound.desktop 44 | desktopSound:setChecked( c.sound.desktop ) 45 | desktopSound.onCheckedChanged = function( checkbox, checked ) 46 | options.desktopSound = checked 47 | c.sound.desktop = checked 48 | end 49 | x = 2 * x + masterVolume:getWidth() 50 | y = margin + label:getHeight() + marginBottom 51 | desktopSound:setPos( x, y ) 52 | end 53 | 54 | function audiooptionspanel:activate() 55 | self:saveControlStates() 56 | end 57 | 58 | function audiooptionspanel:onOK() 59 | self:updateSound() 60 | end 61 | 62 | function audiooptionspanel:onCancel() 63 | self:resetControlStates() 64 | end 65 | 66 | audiooptionspanel.onApply = audiooptionspanel.onOK 67 | 68 | function audiooptionspanel:saveControlStates() 69 | local controls = {} 70 | self.controls = controls 71 | controls.masterVolume = self.masterVolume:getValue() 72 | controls.desktopSound = self.desktopSound:isChecked() 73 | end 74 | 75 | function audiooptionspanel:resetControlStates() 76 | local controls = self.controls 77 | self.masterVolume:setValue( controls.masterVolume ) 78 | self.desktopSound:setChecked( controls.desktopSound ) 79 | table.clear( controls ) 80 | end 81 | 82 | function audiooptionspanel:updateSound() 83 | local options = self.options 84 | convar.setConvar( "snd_volume", options.masterVolume ) 85 | convar.setConvar( "snd_desktop", options.desktopSound and 1 or 0 ) 86 | end 87 | -------------------------------------------------------------------------------- /engine/client/gui/optionsmenu/bindlistheader.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Bind List Header class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.bindlistheader" ( "gui.box" ) 8 | 9 | local bindlistheader = gui.bindlistheader 10 | 11 | function bindlistheader:bindlistheader( parent, name, text ) 12 | gui.box.box( self, parent, name ) 13 | self.width = parent:getWidth() 14 | self.height = 46 15 | self.text = text or "Bind List Header" 16 | 17 | self:setScheme( "Default" ) 18 | end 19 | 20 | function bindlistheader:draw() 21 | love.graphics.setColor( self:getScheme( "label.textColor" ) ) 22 | local font = self:getScheme( "fontBold" ) 23 | love.graphics.setFont( font ) 24 | 25 | local margin = 18 26 | local x = math.round( margin ) 27 | local y = math.round( self:getHeight() / 2 - font:getHeight() / 2 ) 28 | love.graphics.print( self:getText(), x, y ) 29 | 30 | local label = "Key or Button" 31 | x = math.round( self:getWidth() - margin - font:getWidth( label ) ) 32 | love.graphics.print( label, x, y ) 33 | 34 | love.graphics.setColor( self:getScheme( 'bindlistheader.borderColor' ) ) 35 | x = margin 36 | y = self:getHeight() - 6 -- Padding-bottom 37 | local width = self:getWidth() - 2 * margin 38 | local height = 1 39 | love.graphics.rectangle( "fill", x, y, width, height ) 40 | 41 | gui.box.draw( self ) 42 | end 43 | 44 | accessor( bindlistheader, "text" ) 45 | -------------------------------------------------------------------------------- /engine/client/gui/optionsmenu/init.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Options Menu class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.optionsmenu" ( "gui.tabbedframe" ) 8 | 9 | local optionsmenu = gui.optionsmenu 10 | 11 | function optionsmenu:optionsmenu( parent ) 12 | local name = "Options Menu" 13 | gui.tabbedframe.tabbedframe( self, parent, name, "Options" ) 14 | self.resizable = false 15 | 16 | local groupName = name .. " Command Button Group" 17 | local group = gui.commandbuttongroup( self, groupName ) 18 | 19 | local buttonName = nil 20 | buttonName = name .. " OK Button" 21 | self.okButton = gui.commandbutton( group, buttonName, "OK" ) 22 | self.okButton.onClick = function( commandbutton ) 23 | local panels = self:getTabPanels():getChildren() 24 | for _, panel in ipairs( panels ) do 25 | panel:onOK() 26 | end 27 | convar.saveConfig() 28 | self:close() 29 | end 30 | buttonName = name .. " Cancel Button" 31 | self.cancelButton = gui.commandbutton( group, buttonName, "Cancel" ) 32 | self.cancelButton.onClick = function( commandbutton ) 33 | local panels = self:getTabPanels():getChildren() 34 | for _, panel in ipairs( panels ) do 35 | panel:onCancel() 36 | end 37 | self:close() 38 | end 39 | buttonName = name .. " Apply Button" 40 | self.applyButton = gui.commandbutton( group, buttonName, "Apply" ) 41 | self.applyButton.onClick = function( commandbutton ) 42 | local panels = self:getTabPanels():getChildren() 43 | for _, panel in ipairs( panels ) do 44 | panel:onApply() 45 | end 46 | convar.saveConfig() 47 | end 48 | 49 | require( "engine.client.gui.optionsmenu.keyboardoptionspanel" ) 50 | self:addTab( "Keyboard", gui.keyboardoptionspanel ) 51 | 52 | require( "engine.client.gui.optionsmenu.videooptionspanel" ) 53 | self:addTab( "Video", gui.videooptionspanel ) 54 | 55 | require( "engine.client.gui.optionsmenu.audiooptionspanel" ) 56 | self:addTab( "Audio", gui.audiooptionspanel ) 57 | 58 | -- require( "engine.client.gui.optionsmenu.multiplayeroptionspanel" ) 59 | -- self:addTab( "Multiplayer", gui.multiplayeroptionspanel() ) 60 | end 61 | 62 | function optionsmenu:activate() 63 | local panels = self:getTabPanels():getChildren() 64 | for _, panel in ipairs( panels ) do 65 | panel:activate() 66 | end 67 | gui.frame.activate( self ) 68 | end 69 | 70 | function optionsmenu:invalidateLayout() 71 | self:moveToCenter() 72 | gui.tabbedframe.invalidateLayout( self ) 73 | end 74 | 75 | local function onReloadScript() 76 | local mainmenu = g_MainMenu 77 | if ( mainmenu == nil or not mainmenu.optionsMenu ) then 78 | return 79 | end 80 | 81 | local optionsMenu = mainmenu.optionsMenu 82 | local visible = optionsMenu:isVisible() 83 | optionsMenu:remove() 84 | optionsMenu = gui.optionsmenu( mainmenu ) 85 | mainmenu.optionsMenu = optionsMenu 86 | optionsMenu:moveToCenter() 87 | if ( visible ) then 88 | optionsMenu:activate() 89 | end 90 | end 91 | 92 | onReloadScript() 93 | -------------------------------------------------------------------------------- /engine/client/gui/optionsmenu/keyboardoptionsadvancedframe.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Advanced Keyboard Options Frame class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.keyboardoptionsadvancedframe" ( "gui.frame" ) 8 | 9 | local keyboardoptionsadvancedframe = gui.keyboardoptionsadvancedframe 10 | 11 | function keyboardoptionsadvancedframe:keyboardoptionsadvancedframe( parent, name, title ) 12 | name = name or "Advanced Keyboard Options Frame" 13 | title = title or "Advanced Keyboard Options" 14 | gui.frame.frame( self, parent, name, title ) 15 | self:setWidth( 480 ) 16 | self:setHeight( 147 ) 17 | self:setResizable( false ) 18 | 19 | name = "Developer Console" 20 | self.console = gui.checkbox( self, name, "Enable Developer Console" ) 21 | self.console:setChecked( convar.getConvar( "con_enable" ):getBoolean() ) 22 | self.console.onCheckedChanged = function( checkbox, checked ) 23 | convar.setConvar( "con_enable", checked and "1" or "0" ) 24 | end 25 | 26 | local margin = 36 27 | local titleBarHeight = 86 28 | self.console:setPos( margin, titleBarHeight ) 29 | end 30 | -------------------------------------------------------------------------------- /engine/client/gui/optionsmenu/keyboardoptionscommandbuttongroup.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Keyboard Options Command Button Group class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.keyboardoptionscommandbuttongroup" ( "gui.commandbuttongroup" ) 8 | 9 | local keyboardoptionscommandbuttongroup = gui.keyboardoptionscommandbuttongroup 10 | 11 | function keyboardoptionscommandbuttongroup:keyboardoptionscommandbuttongroup( parent, name ) 12 | gui.commandbuttongroup.commandbuttongroup( self, parent, name ) 13 | end 14 | 15 | function keyboardoptionscommandbuttongroup:invalidateLayout() 16 | local children = self:getChildren() 17 | local width = 0 18 | if ( children ) then 19 | for i, commandbutton in ipairs( children ) do 20 | commandbutton:setX( width ) 21 | width = width + commandbutton:getWidth() 22 | end 23 | end 24 | self:setWidth( width ) 25 | 26 | local parent = self:getParent() 27 | local margin = 24 28 | self:setPos( margin, parent:getHeight() - self:getHeight() ) 29 | gui.panel.invalidateLayout( self ) 30 | end 31 | -------------------------------------------------------------------------------- /engine/client/gui/optionsmenu/keyboardoptionspanel.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Keyboard Options Panel class 4 | -- 5 | --==========================================================================-- 6 | 7 | require( "engine.client.gui.optionsmenu.bindlistpanel" ) 8 | require( "engine.client.gui.optionsmenu.keyboardoptionscommandbuttongroup" ) 9 | require( "engine.client.gui.optionsmenu.keyboardoptionsadvancedframe" ) 10 | 11 | class "gui.keyboardoptionspanel" ( "gui.frametabpanel" ) 12 | 13 | local keyboardoptionspanel = gui.keyboardoptionspanel 14 | 15 | function keyboardoptionspanel:keyboardoptionspanel( parent, name ) 16 | name = name or "Keyboard Options Panel" 17 | gui.frametabpanel.frametabpanel( self, parent, name ) 18 | 19 | self.bindList = gui.bindlistpanel( self ) 20 | local margin = 24 21 | local height = 348 - margin 22 | self.bindList:setDimensions( 640 - 2 * margin, height ) 23 | self.bindList:setMargin( margin ) 24 | self.bindList:readBinds() 25 | 26 | local name = "Keyboard Options" 27 | local groupName = name .. " Command Button Group" 28 | local group = gui.keyboardoptionscommandbuttongroup( self, groupName ) 29 | 30 | local buttonName = name .. " Use Defaults Button" 31 | self.useDefaultsButton = gui.commandbutton( group, buttonName, "Use Defaults" ) 32 | self.useDefaultsButton.onClick = function( commandbutton ) 33 | self.bindList:useDefaults() 34 | end 35 | buttonName = name .. " Advanced Button" 36 | self.advancedButton = gui.commandbutton( group, buttonName, "Advanced" ) 37 | self.advancedButton.onClick = function( commandbutton ) 38 | if ( self.advancedOptions == nil ) then 39 | self.advancedOptions = gui.keyboardoptionsadvancedframe( g_MainMenu ) 40 | self.advancedOptions:activate() 41 | self.advancedOptions:moveToCenter() 42 | else 43 | self.advancedOptions:activate() 44 | end 45 | end 46 | end 47 | 48 | function keyboardoptionspanel:activate() 49 | end 50 | 51 | function keyboardoptionspanel:onOK() 52 | self.bindList:saveBinds() 53 | end 54 | 55 | function keyboardoptionspanel:onCancel() 56 | local innerPanel = self.bindList:getInnerPanel() 57 | innerPanel:removeChildren() 58 | self.bindList:readBinds() 59 | end 60 | 61 | keyboardoptionspanel.onApply = keyboardoptionspanel.onOK 62 | -------------------------------------------------------------------------------- /engine/client/gui/optionsmenu/multiplayeroptionspanel.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Multiplayer Options Panel class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.multiplayeroptionspanel" ( "gui.frametabpanel" ) 8 | 9 | local multiplayeroptionspanel = gui.multiplayeroptionspanel 10 | 11 | function multiplayeroptionspanel:multiplayeroptionspanel( parent, name ) 12 | name = name or "Multiplayer Options Panel" 13 | gui.frametabpanel.frametabpanel( self, parent, name ) 14 | local options = {} 15 | self.options = options 16 | local c = config.getConfig() 17 | 18 | local e = gui.createElement 19 | 20 | local panel = e( "box", { 21 | parent = self, 22 | position = "absolute", 23 | margin = 36 24 | }, { 25 | e( "text", { text = "Name", marginBottom = 9 } ), 26 | e( "textbox", { position = "static", text = "Unnamed" } ) 27 | } ) 28 | 29 | panel:setPos( panel:getMarginLeft(), panel:getMarginTop() ) 30 | 31 | -- name = "Play Sound in Desktop" 32 | -- local desktopSound = gui.checkbox( self, name, name ) 33 | -- self.desktopSound = desktopSound 34 | -- options.desktopSound = c.sound.desktop 35 | -- desktopSound:setChecked( c.sound.desktop ) 36 | -- desktopSound.onCheckedChanged = function( checkbox, checked ) 37 | -- options.desktopSound = checked 38 | -- c.sound.desktop = checked 39 | -- end 40 | -- x = 2 * x + self.name:getWidth() 41 | -- y = margin + label:getHeight() + marginBottom 42 | -- desktopSound:setPos( x, y ) 43 | 44 | -- name = "Tickrates" 45 | -- local radiobuttongroup = gui.radiobuttongroup( self, name ) 46 | -- name = "20" 47 | -- local radiobutton = gui.radiobutton( self, name .. " 1" ) 48 | -- radiobuttongroup:addItem( radiobutton ) 49 | -- x = x 50 | -- radiobutton:setPos( x, y ) 51 | -- radiobutton:setDefault( true ) 52 | end 53 | 54 | function multiplayeroptionspanel:activate() 55 | self:saveControlStates() 56 | end 57 | 58 | function multiplayeroptionspanel:onOK() 59 | self:updateOptions() 60 | end 61 | 62 | function multiplayeroptionspanel:onCancel() 63 | self:resetControlStates() 64 | end 65 | 66 | multiplayeroptionspanel.onApply = multiplayeroptionspanel.onOK 67 | 68 | function multiplayeroptionspanel:saveControlStates() 69 | local controls = {} 70 | self.controls = controls 71 | -- controls.name = self.name:getText() 72 | -- controls.desktopSound = self.desktopSound:isChecked() 73 | end 74 | 75 | function multiplayeroptionspanel:resetControlStates() 76 | local controls = self.controls 77 | -- self.name:setText( controls.name ) 78 | -- self.desktopSound:setChecked( controls.desktopSound ) 79 | table.clear( controls ) 80 | end 81 | 82 | function multiplayeroptionspanel:updateOptions() 83 | local options = self.options 84 | convar.setConvar( "name", options.name ) 85 | end 86 | -------------------------------------------------------------------------------- /engine/client/gui/passwordtextbox.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Password Text Box class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.passwordtextbox" ( "gui.textbox" ) 8 | 9 | local passwordtextbox = gui.passwordtextbox 10 | 11 | local function getInnerWidth( self ) 12 | return self:getWidth() - 2 * self.padding 13 | end 14 | 15 | function passwordtextbox:passwordtextbox( parent, name, placeholder ) 16 | gui.textbox.textbox( self, parent, name, placeholder or "Password" ) 17 | self.password = "" 18 | end 19 | 20 | local utf8sub = string.utf8sub 21 | 22 | function passwordtextbox:doBackspace( count ) 23 | count = count or 1 24 | 25 | if ( count == 0 ) then 26 | count = self.cursorPos + 1 27 | end 28 | 29 | if ( self.cursorPos > 0 ) then 30 | local sub1 = utf8sub( self.password, 1, self.cursorPos - count ) 31 | if ( sub1 == self.password ) then 32 | sub1 = "" 33 | end 34 | 35 | local sub2 = utf8sub( self.password, self.cursorPos + 1 ) 36 | self.password = sub1 .. sub2 37 | end 38 | 39 | gui.textbox.doBackspace( self, count ) 40 | end 41 | 42 | local utf8len = string.utf8len 43 | 44 | function passwordtextbox:doDelete( count ) 45 | count = count or 1 46 | 47 | if ( count == 0 ) then 48 | count = utf8len( self.password ) - self.cursorPos 49 | end 50 | 51 | local sub1 = utf8sub( self.password, 1, self.cursorPos ) 52 | if ( self.cursorPos == 0 ) then 53 | sub1 = "" 54 | end 55 | 56 | local sub2 = utf8sub( self.password, self.cursorPos + 1 + count ) 57 | self.password = sub1 .. sub2 58 | gui.textbox.doDelete( self, count ) 59 | end 60 | 61 | function passwordtextbox:doCut() 62 | end 63 | 64 | function passwordtextbox:doCopy() 65 | end 66 | 67 | function passwordtextbox:getAutocomplete() 68 | end 69 | 70 | accessor( passwordtextbox, "password" ) 71 | 72 | function passwordtextbox:insertText( text ) 73 | local buffer = {} 74 | for i = 1, string.utf8len( text ) do 75 | table.insert( buffer, "•" ) 76 | end 77 | 78 | local sub1 = utf8sub( self.password, self.cursorPos + 1 ) 79 | local sub2 = utf8sub( self.password, 1, self.cursorPos ) 80 | if ( self.cursorPos == 0 ) then 81 | sub2 = "" 82 | end 83 | 84 | self.password = sub2 .. text .. sub1 85 | gui.textbox.insertText( self, table.concat( buffer ) ) 86 | end 87 | 88 | function passwordtextbox:isMultiline() 89 | return false 90 | end 91 | 92 | function passwordtextbox:setMultiline( multiline ) 93 | assert( false ) 94 | end 95 | 96 | function passwordtextbox:setText( text ) 97 | local buffer = {} 98 | for i = 1, string.utf8len( text ) do 99 | table.insert( buffer, "•" ) 100 | end 101 | gui.textbox.setText( self, table.concat( buffer ) ) 102 | self.password = text 103 | end 104 | 105 | passwordtextbox.setPassword = passwordtextbox.setText 106 | -------------------------------------------------------------------------------- /engine/client/gui/progressbar.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Progress Bar class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.progressbar" ( "gui.box" ) 8 | 9 | local progressbar = gui.progressbar 10 | 11 | function progressbar:progressbar( parent, name ) 12 | gui.box.box( self, parent, name ) 13 | self:setDisplay( "block" ) 14 | self:setBackgroundColor( self:getScheme( "progressbar.backgroundColor" ) ) 15 | self.width = 216 16 | self.height = 2 17 | end 18 | 19 | function progressbar:draw() 20 | gui.panel.drawBackground( self, self:getScheme( "progressbar.backgroundColor" ) ) 21 | self:drawBar() 22 | 23 | gui.box.draw( self ) 24 | end 25 | 26 | function progressbar:drawBar() 27 | local color = self:getScheme( "progressbar.foregroundColor" ) 28 | local value = self:getValue() 29 | local min = self:getMin() 30 | local max = self:getMax() 31 | local percent = math.remap( value, min, max, 0, 1 ) 32 | local width = self:getWidth() * percent 33 | local height = self:getHeight() 34 | love.graphics.setColor( color ) 35 | love.graphics.rectangle( "fill", 0, 0, width, height ) 36 | end 37 | 38 | gui.accessor( progressbar, "min", nil, nil, 0 ) 39 | gui.accessor( progressbar, "max", nil, nil, 1 ) 40 | gui.accessor( progressbar, "value", nil, nil, 0 ) 41 | -------------------------------------------------------------------------------- /engine/client/gui/radiobuttongroup.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Radio Button Group class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.radiobuttongroup" ( "gui.box" ) 8 | 9 | local radiobuttongroup = gui.radiobuttongroup 10 | 11 | function radiobuttongroup:radiobuttongroup( parent, name ) 12 | gui.box.box( self, parent, name ) 13 | self:setDisplay( "block" ) 14 | self:setPosition( "absolute" ) 15 | self.selectedId = 0 16 | self.disabled = false 17 | end 18 | 19 | function radiobuttongroup:addItem( item ) 20 | self.items = self.items or {} 21 | item.id = #self.items + 1 22 | table.insert( self.items, item ) 23 | item.group = self 24 | end 25 | 26 | function radiobuttongroup:removeItem( item ) 27 | local items = self:getItems() 28 | for i, v in ipairs( items ) do 29 | if ( v == item ) then 30 | table.remove( items, i ) 31 | if ( self.selectedId == i ) then 32 | self.selectedId = 0 33 | end 34 | self:invalidateLayout() 35 | end 36 | end 37 | 38 | if ( #items == 0 ) then 39 | self.items = nil 40 | end 41 | end 42 | 43 | accessor( radiobuttongroup, "items" ) 44 | accessor( radiobuttongroup, "selectedId" ) 45 | 46 | function radiobuttongroup:getSelectedItem() 47 | local items = self:getItems() 48 | if ( items ) then 49 | return items[ self:getSelectedId() ] 50 | end 51 | end 52 | 53 | function radiobuttongroup:getValue() 54 | local item = self:getSelectedItem() 55 | if ( item ) then 56 | return item:getValue() 57 | end 58 | end 59 | 60 | accessor( radiobuttongroup, "disabled", "is" ) 61 | 62 | function radiobuttongroup:setSelectedId( selectedId, default ) 63 | local oldSelectedId = self:getSelectedId() 64 | local items = self:getItems() 65 | local oldSelection = items[ oldSelectedId ] 66 | local newSelection = items[ selectedId ] 67 | if ( oldSelection and oldSelectedId ~= selectedId ) then 68 | oldSelection:setSelected( false ) 69 | newSelection:setSelected( true ) 70 | self.selectedId = selectedId 71 | self:onValueChanged( oldSelection:getValue(), newSelection:getValue() ) 72 | else 73 | newSelection:setSelected( true ) 74 | self.selectedId = selectedId 75 | if ( not default ) then 76 | self:onValueChanged( nil, newSelection:getValue() ) 77 | end 78 | end 79 | end 80 | 81 | function radiobuttongroup:setValue( value ) 82 | local items = self:getItems() 83 | for i, v in ipairs( items ) do 84 | if ( v:getValue() == value ) then 85 | self:setSelectedId( i ) 86 | end 87 | end 88 | end 89 | 90 | function radiobuttongroup:onValueChanged( oldValue, newValue ) 91 | end 92 | -------------------------------------------------------------------------------- /engine/client/gui/rootpanel.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Root Panel class 4 | -- 5 | --==========================================================================-- 6 | 7 | require( "engine.client.gui.box" ) 8 | 9 | class "gui.rootpanel" ( "gui.box" ) 10 | 11 | local rootpanel = gui.rootpanel 12 | 13 | function rootpanel:rootpanel() 14 | self.x = 0 15 | self.y = 0 16 | self.width = love.graphics.getWidth() 17 | self.height = love.graphics.getHeight() 18 | self.name = "Root Panel" 19 | self.visible = true 20 | self.children = {} 21 | self.scale = 1 22 | self.opacity = 1 23 | self:setUseFullscreenCanvas( true ) 24 | end 25 | 26 | function rootpanel:invalidateLayout() 27 | self:setDimensions( love.graphics.getWidth(), love.graphics.getHeight() ) 28 | 29 | gui.panel.invalidateLayout( self ) 30 | end 31 | -------------------------------------------------------------------------------- /engine/client/gui/scheme.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Scheme class 4 | -- 5 | --==========================================================================-- 6 | 7 | class( "scheme" ) 8 | 9 | scheme._schemes = scheme._schemes or {} 10 | 11 | local properties = {} 12 | 13 | function scheme.clear( name ) 14 | properties[ name ] = {} 15 | end 16 | 17 | function scheme.getProperty( name, property ) 18 | local cachedProperty = properties[ name ][ property ] 19 | if ( cachedProperty ) then 20 | return cachedProperty 21 | end 22 | 23 | local value = scheme._schemes[ name ] 24 | local type = type( value ) 25 | if ( type ~= "scheme" ) then 26 | error( "attempt to index scheme '" .. name .. "' " .. 27 | "(a " .. type .. " value)", 3 ) 28 | end 29 | 30 | for key in string.gmatch( property .. ".", "(%w-)%." ) do 31 | if ( value and value[ key ] ) then 32 | value = value[ key ] 33 | else 34 | error( "attempt to index property '" .. property .. "' " .. 35 | "(a nil value)", 3 ) 36 | end 37 | end 38 | 39 | properties[ name ][ property ] = value 40 | return value 41 | end 42 | 43 | function scheme.isLoaded( name ) 44 | return scheme._schemes[ name ] ~= nil 45 | end 46 | 47 | function scheme.load( name ) 48 | require( "schemes." .. name ) 49 | end 50 | 51 | function scheme:scheme( name ) 52 | self.name = name 53 | scheme._schemes[ name ] = self 54 | scheme.clear( name ) 55 | end 56 | 57 | function scheme:__tostring() 58 | return "scheme: \"" .. self.name .. "\"" 59 | end 60 | -------------------------------------------------------------------------------- /engine/client/gui/scrollablepanel.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Scrollable Panel class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.scrollablepanel" ( "gui.box" ) 8 | 9 | local scrollablepanel = gui.scrollablepanel 10 | 11 | function scrollablepanel:scrollablepanel( parent, name ) 12 | gui.box.box( self, parent, name ) 13 | self.panel = gui.box( self, self:getName() .. " Inner Panel" ) 14 | self.panel:setPosition( "absolute" ) 15 | 16 | self.scrollbar = gui.scrollbar( self, self:getName() .. " Scrollbar" ) 17 | self.scrollbar.onValueChanged = function( _, oldValue, newValue ) 18 | self.panel:setY( -newValue ) 19 | end 20 | 21 | self:setScheme( "Default" ) 22 | end 23 | 24 | accessor( scrollablepanel, "innerHeight" ) 25 | accessor( scrollablepanel, "innerPanel", nil, "panel" ) 26 | accessor( scrollablepanel, "scrollbar" ) 27 | 28 | function scrollablepanel:invalidateLayout() 29 | self:setDimensions( self:getDimensions() ) 30 | gui.panel.invalidateLayout( self ) 31 | end 32 | 33 | local function getParentFrame( self ) 34 | local panel = self 35 | while ( panel ~= nil ) do 36 | panel = panel:getParent() 37 | if ( typeof( panel, "frame" ) ) then 38 | return panel 39 | end 40 | end 41 | end 42 | 43 | function scrollablepanel:keypressed( key, scancode, isrepeat ) 44 | local parentFrame = getParentFrame( self ) 45 | local parentFocus = parentFrame and parentFrame.focus 46 | if ( key == "tab" and ( self.focus or parentFocus ) ) then 47 | gui.frame.moveFocus( self.panel ) 48 | end 49 | 50 | return gui.panel.keypressed( self, key, scancode, isrepeat ) 51 | end 52 | 53 | function scrollablepanel:setWidth( width ) 54 | self.panel:setWidth( width ) 55 | gui.panel.setWidth( self, width ) 56 | end 57 | 58 | function scrollablepanel:setHeight( height ) 59 | self.scrollbar:setRangeWindow( height ) 60 | gui.panel.setHeight( self, height ) 61 | end 62 | 63 | function scrollablepanel:setInnerHeight( innerHeight ) 64 | self.innerHeight = innerHeight 65 | self.panel:setHeight( innerHeight ) 66 | self.scrollbar:setRange( 0, innerHeight ) 67 | end 68 | 69 | function scrollablepanel:wheelmoved( x, y ) 70 | local panel = self:getInnerPanel() 71 | if ( panel.mouseover or panel:isChildMousedOver() ) then 72 | local scrollbar = self:getScrollbar() 73 | if ( scrollbar ) then 74 | local font = self:getScheme( "font" ) 75 | if ( y < 0 ) then 76 | gui.setFocusedPanel( nil, false ) 77 | scrollbar:scrollDown( 3 * font:getHeight() ) 78 | return true 79 | elseif ( y > 0 ) then 80 | gui.setFocusedPanel( nil, false ) 81 | scrollbar:scrollUp( 3 * font:getHeight() ) 82 | return true 83 | end 84 | end 85 | end 86 | 87 | return gui.panel.wheelmoved( self, x, y ) 88 | end 89 | -------------------------------------------------------------------------------- /engine/client/gui/text.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Text class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.text" ( "gui.box" ) 8 | 9 | local text = gui.text 10 | 11 | function text:text( parent, text ) 12 | gui.box.box( self, parent, nil ) 13 | self:set( text ) 14 | end 15 | 16 | function text:createCanvas() 17 | end 18 | 19 | function text:draw() 20 | assert( false ) 21 | love.graphics.setColor( self:getColor() ) 22 | love.graphics.draw( self._text ) 23 | end 24 | 25 | function text:drawCanvas() 26 | if ( not self:isVisible() ) then 27 | return 28 | end 29 | 30 | love.graphics.push() 31 | local a = self:getOpacity() 32 | local c = self:getColor() 33 | love.graphics.setColor( 34 | a * c[ 1 ], a * c[ 2 ], a * c[ 3 ], a * c[ 4 ] 35 | ) 36 | love.graphics.draw( self._text ) 37 | love.graphics.pop() 38 | end 39 | 40 | function text:getWidth() 41 | local font = self:getFont() 42 | return font:getWidth( self:get() ) 43 | end 44 | 45 | function text:getHeight() 46 | local font = self:getFont() 47 | return font:getHeight() 48 | end 49 | 50 | function text:set( text ) 51 | self.text = text 52 | 53 | if ( self._text ) then 54 | self._text:set( text or "" ) 55 | else 56 | self._text = love.graphics.newText( self:getFont(), text ) 57 | end 58 | end 59 | 60 | text.setText = text.set 61 | 62 | function text:get() 63 | return rawget( self, "text" ) or "" 64 | end 65 | 66 | function text:setFont( font ) 67 | self._text:setFont( font ) 68 | end 69 | 70 | function text:getFont() 71 | return self._text and self._text:getFont() or self:getScheme( "font" ) 72 | end 73 | -------------------------------------------------------------------------------- /engine/client/gui/textboxautocompleteitemgroup.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Text Box Autocomplete Item Group class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.textboxautocompleteitemgroup" ( "gui.dropdownlistitemgroup" ) 8 | 9 | local textboxautocompleteitemgroup = gui.textboxautocompleteitemgroup 10 | 11 | function textboxautocompleteitemgroup:textboxautocompleteitemgroup( parent, name ) 12 | gui.dropdownlistitemgroup.dropdownlistitemgroup( self, parent, name ) 13 | -- UNDONE: The drop-down list field is reserved for the control responsible 14 | -- for the drop-down list item group. The control does not necessarily have 15 | -- to be a dropdownlist. 16 | -- self.dropDownList = nil 17 | self.textbox = parent 18 | end 19 | 20 | function textboxautocompleteitemgroup:addItem( item ) 21 | item:setParent( self ) 22 | gui.radiobuttongroup.addItem( self, item ) 23 | 24 | item.onClick = function( item ) 25 | local value = item:getValue() 26 | self:removeChildren() 27 | local textbox = self:getTextbox() 28 | textbox:setText( value ) 29 | end 30 | 31 | self:invalidateLayout() 32 | end 33 | 34 | accessor( textboxautocompleteitemgroup, "textbox" ) 35 | 36 | function textboxautocompleteitemgroup:isVisible() 37 | local textbox = self:getTextbox() 38 | local children = self:getChildren() 39 | local hasChildren = children and #children > 0 40 | return textbox:isVisible() and textbox.focus and hasChildren 41 | end 42 | 43 | function textboxautocompleteitemgroup:mousepressed( x, y, button, istouch ) 44 | if ( button == 1 ) then 45 | local textbox = self:getTextbox() 46 | if ( textbox ~= gui._topPanel and 47 | ( not ( self.mouseover or self:isChildMousedOver() ) ) ) then 48 | if ( self:getChildren() ) then 49 | self:removeChildren() 50 | end 51 | end 52 | end 53 | 54 | return gui.panel.mousepressed( self, x, y, button, istouch ) 55 | end 56 | 57 | function textboxautocompleteitemgroup:onValueChanged( oldValue, newValue ) 58 | end 59 | -------------------------------------------------------------------------------- /engine/client/gui/throbber.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Throbber class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.throbber" ( "gui.imagepanel" ) 8 | 9 | local throbber = gui.throbber 10 | 11 | function throbber:throbber( parent, name, image ) 12 | gui.imagepanel.imagepanel( self, parent, name, image or "images/gui/throbber.png" ) 13 | self:setDimensions( 16, 16 ) 14 | self:setOpacity( 0 ) 15 | end 16 | 17 | local missingImage = false 18 | 19 | function throbber:draw() 20 | gui.panel._maskedPanel = self 21 | love.graphics.stencil( gui.panel.drawMask ) 22 | love.graphics.setStencilTest( "greater", 0 ) 23 | love.graphics.setColor( self:getColor() ) 24 | local image = self:getImage() 25 | local width = self:getWidth() 26 | local height = self:getHeight() 27 | love.graphics.draw( 28 | image, 29 | width / 2, 30 | height / 2, 31 | love.timer.getTime() % 2 * math.pi, 32 | 1, 33 | 1, 34 | width / 2, 35 | height / 2 36 | ) 37 | love.graphics.setStencilTest() 38 | 39 | missingImage = image == nil 40 | if ( missingImage ) then 41 | self:drawMissingImage() 42 | end 43 | 44 | gui.panel.draw( self ) 45 | end 46 | 47 | function throbber:enable() 48 | self.enabled = true 49 | end 50 | 51 | function throbber:disable() 52 | self.enabled = false 53 | end 54 | 55 | accessor( throbber, "enabled", "is" ) 56 | 57 | function throbber:update( dt ) 58 | local opacity = self:getOpacity() 59 | if ( self:isVisible() and opacity ~= 0 ) then 60 | self:invalidate() 61 | end 62 | 63 | -- FIXME: self:animate doesn't work for gui.throbber. 64 | if ( self.enabled and opacity ~= 1 ) then 65 | self:setOpacity( math.min( opacity + dt * ( 1 / 0.4 ), 1 ) ) 66 | elseif ( opacity ~= 0 ) then 67 | self:setOpacity( math.max( opacity - dt * ( 1 / 0.4 ), 0 ) ) 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /engine/client/gui/viewport.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Viewport Panel class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.viewport" ( "gui.box" ) 8 | 9 | local viewport = gui.viewport 10 | 11 | function viewport:viewport( parent ) 12 | gui.box.box( self, parent, "Viewport" ) 13 | self.width = love.graphics.getWidth() 14 | self.height = love.graphics.getHeight() 15 | self:setUseFullscreenCanvas( true ) 16 | self:moveToBack() 17 | end 18 | 19 | function viewport:invalidateLayout() 20 | self:setDimensions( love.graphics.getWidth(), love.graphics.getHeight() ) 21 | 22 | gui.panel.invalidateLayout( self ) 23 | end 24 | 25 | local VIEWPORT_ANIM_TIME = 0.2 26 | 27 | function viewport:hide() 28 | self:animate( { 29 | opacity = 0, 30 | }, VIEWPORT_ANIM_TIME, "easeOutQuint", function() 31 | self:setVisible( false ) 32 | self:setOpacity( 1 ) 33 | end ) 34 | end 35 | 36 | function viewport:show() 37 | if ( not self:isVisible() ) then 38 | self:setOpacity( 0 ) 39 | self:animate( { 40 | opacity = 1 41 | }, VIEWPORT_ANIM_TIME, "easeOutQuint" ) 42 | end 43 | 44 | self:setVisible( true ) 45 | end 46 | 47 | local function hideViewport() 48 | if ( g_Viewport == nil ) then 49 | return 50 | end 51 | 52 | g_Viewport:hide() 53 | end 54 | 55 | hook.set( "client", hideViewport, "onMainMenuActivate", "hideViewport" ) 56 | 57 | local function showViewport() 58 | if ( g_Viewport == nil ) then 59 | return 60 | end 61 | 62 | g_Viewport:show() 63 | end 64 | 65 | hook.set( "client", showViewport, "onMainMenuClose", "showViewport" ) 66 | -------------------------------------------------------------------------------- /engine/client/gui/watch.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Watch class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.watch" ( "gui.label" ) 8 | 9 | local watch = gui.watch 10 | 11 | function watch:watch( parent, name ) 12 | gui.label.label( self, parent, name, text ) 13 | 14 | self:setScheme( "Console" ) 15 | self.font = self:getScheme( "font" ) 16 | local margin = gui.scale( 96 ) 17 | self.width = love.graphics.getWidth() - 2 * margin 18 | self.height = self.font:getHeight() 19 | 20 | self:setScheme( "Default" ) 21 | self:invalidateLayout() 22 | end 23 | 24 | function watch:update( dt ) 25 | -- HACKHACK: Fade this out for readability. 26 | if ( g_HudMoveIndicator and g_HudMoveIndicator._entity ) then 27 | self:setOpacity( 0 ) 28 | else 29 | self:setOpacity( 1 ) 30 | end 31 | 32 | local f, err = loadstring( "return " .. self:getExpression() ) 33 | local ret = "?" 34 | if ( f ) then 35 | local status, err = pcall( f ) 36 | if ( status == false ) then 37 | self:invalidate() 38 | return 39 | end 40 | else 41 | ret = err 42 | end 43 | 44 | self:setText( self:getExpression() .. " = " .. tostring( 45 | f and f() or ret 46 | ) ) 47 | self:invalidate() 48 | end 49 | 50 | gui.accessor( watch, "expression" ) 51 | 52 | function watch:invalidateLayout() 53 | local margin = gui.scale( 96 ) 54 | local x = margin 55 | local y = margin + 28 56 | self:setPos( x, y ) 57 | 58 | gui.panel.invalidateLayout( self ) 59 | end 60 | -------------------------------------------------------------------------------- /engine/client/input.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Input interface 4 | -- 5 | --==========================================================================-- 6 | 7 | local love = love 8 | 9 | module( "input" ) 10 | 11 | function getKeyTrap() 12 | return _keyTrap 13 | end 14 | 15 | local mx, my = 0, 0 16 | 17 | function setKeyTrap( callback ) 18 | _keyTrap = callback 19 | mx, my = love.mouse.getPosition() 20 | end 21 | 22 | function isKeyTrapped( key ) 23 | local callback = getKeyTrap() 24 | if ( callback ) then 25 | love.mouse.setPosition( mx, my ) 26 | setKeyTrap() 27 | return callback( key ) 28 | else 29 | return false 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /engine/client/network/localhost_enet_peer.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Localhost ENet Peer class 4 | -- 5 | --==========================================================================-- 6 | 7 | class( "localhost_enet_peer" ) 8 | 9 | function localhost_enet_peer:localhost_enet_peer() 10 | g_localhost_enet_peer = self 11 | end 12 | 13 | function localhost_enet_peer:connect_id() 14 | return -1 15 | end 16 | 17 | function localhost_enet_peer:disconnect( data ) 18 | local event = { 19 | peer = g_localhost_enet_server, 20 | type = "disconnect", 21 | data = data 22 | } 23 | engine.client.onDisconnect( event ) 24 | end 25 | 26 | function localhost_enet_peer:disconnect_now( data ) 27 | self:disconnect( data ) 28 | end 29 | 30 | function localhost_enet_peer:disconnect_later( data ) 31 | self:disconnect( data ) 32 | end 33 | 34 | function localhost_enet_peer:index() 35 | return -1 36 | end 37 | 38 | function localhost_enet_peer:ping() 39 | end 40 | 41 | function localhost_enet_peer:ping_inverval( interval ) 42 | end 43 | 44 | function localhost_enet_peer:reset() 45 | end 46 | 47 | function localhost_enet_peer:send( data, channel, flag ) 48 | local event = { 49 | peer = g_localhost_enet_server, 50 | type = "receive", 51 | data = data 52 | } 53 | engine.client.onReceive( event ) 54 | end 55 | 56 | function localhost_enet_peer:state() 57 | return "unknown" 58 | end 59 | 60 | function localhost_enet_peer:receive() 61 | end 62 | 63 | function localhost_enet_peer:round_trip_time( value ) 64 | end 65 | 66 | function localhost_enet_peer:last_round_trip_time( value ) 67 | end 68 | 69 | function localhost_enet_peer:throttle_configure( interval, acceleration, deceleration ) 70 | end 71 | 72 | function localhost_enet_peer:timeout( limit, minimum, maximum ) 73 | end 74 | 75 | function localhost_enet_peer:__tostring() 76 | return "127.0.0.1:" .. convar.getConvar( "host_port" ):getNumber() 77 | end 78 | -------------------------------------------------------------------------------- /engine/client/network/localhost_enet_server.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Localhost ENet Server class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "localhost_enet_server" ( "localhost_enet_peer" ) 8 | 9 | function localhost_enet_server:localhost_enet_server() 10 | g_localhost_enet_server = self 11 | end 12 | 13 | function localhost_enet_server:disconnect( data ) 14 | local event = { 15 | peer = g_localhost_enet_peer, 16 | type = "disconnect", 17 | data = data 18 | } 19 | g_localhost_enet_peer = nil 20 | engine.server.onDisconnect( event ) 21 | end 22 | 23 | function localhost_enet_server:send( data, channel, flag ) 24 | local event = { 25 | peer = g_localhost_enet_peer, 26 | type = "receive", 27 | data = data 28 | } 29 | engine.server.onReceive( event ) 30 | end 31 | 32 | function localhost_enet_server:__tostring() 33 | return "127.0.0.1:" .. convar.getConvar( "host_port" ):getNumber() 34 | end 35 | -------------------------------------------------------------------------------- /engine/client/payloads.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Engine client payload handlers 4 | -- 5 | --==========================================================================-- 6 | 7 | local function onReceivePlayerInitialized( payload ) 8 | localplayer = player.getById( payload:get( "id" ) ) 9 | 10 | g_MainMenu:close() 11 | 12 | require( "engine.client.camera" ) 13 | camera.setParentEntity( localplayer ) 14 | camera.setZoom( 2 ) 15 | 16 | if ( not _SERVER ) then 17 | localplayer:setGraphicsSize( love.graphics.getDimensions() ) 18 | localplayer:initialSpawn() 19 | end 20 | end 21 | 22 | payload.setHandler( onReceivePlayerInitialized, "playerInitialized" ) 23 | 24 | local function onReceiveServerInfo( payload ) 25 | local mapName = payload:get( "map" ) 26 | 27 | require( "engine.shared.map" ) 28 | if ( not map.exists( mapName ) ) then 29 | engine.client.download( "maps/" .. mapName .. ".lua" ) 30 | else 31 | map.load( mapName ) 32 | 33 | require( "game" ) 34 | require( "game.client" ) 35 | game.client.load( args ) 36 | 37 | engine.client.sendClientInfo() 38 | end 39 | end 40 | 41 | payload.setHandler( onReceiveServerInfo, "serverInfo" ) 42 | -------------------------------------------------------------------------------- /engine/server/handlers.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Engine server handlers 4 | -- 5 | --==========================================================================-- 6 | 7 | local debug = debug 8 | local engine = engine 9 | local ipairs = ipairs 10 | local love = love 11 | local print = print 12 | local require = require 13 | local tostring = tostring 14 | local unrequire = unrequire 15 | local _G = _G 16 | 17 | module( "engine.server" ) 18 | 19 | function load( arg ) 20 | require( "engine.server.network" ) 21 | 22 | local network = engine.server.network 23 | local initialized = network.initializeServer() 24 | if ( not initialized ) then 25 | return false 26 | end 27 | 28 | _G._SERVER = true 29 | 30 | require( "game" ) 31 | require( "game.server" ) 32 | 33 | local game = _G.game.server 34 | game.load( arg ) 35 | 36 | return true 37 | end 38 | 39 | function quit() 40 | -- Shutdown game 41 | local game = _G.game and _G.game.server or nil 42 | if ( game ) then 43 | game.shutdown() 44 | unrequire( "game.server" ) 45 | _G.game.server = nil 46 | end 47 | 48 | unrequire( "game" ) 49 | _G.game = nil 50 | 51 | -- Shutdown server 52 | local network = engine.server.network 53 | network.shutdownServer() 54 | 55 | unrequire( "engine.server.network" ) 56 | engine.server.network = nil 57 | unrequire( "engine.server.payloads" ) 58 | unrequire( "engine.server.handlers" ) 59 | 60 | _G._SERVER = nil 61 | end 62 | 63 | function update( dt ) 64 | local game = _G.game and _G.game.server or nil 65 | local entity = _G.entity 66 | 67 | if ( game ) then 68 | game.update( dt ) 69 | 70 | if ( entity ) then 71 | local entities = entity.getAll() 72 | for _, entity in ipairs( entities ) do 73 | entity:update( dt ) 74 | end 75 | end 76 | end 77 | 78 | local network = engine.server.network 79 | if ( network ) then 80 | network.update( dt ) 81 | end 82 | end 83 | 84 | local function error_printer(msg, layer) 85 | print((debug.traceback("Error: " .. 86 | tostring(msg), 1+(layer or 1)):gsub("\n[^\n]+$", ""))) 87 | end 88 | 89 | function errhand(msg) 90 | msg = tostring(msg) 91 | 92 | error_printer(msg, 2) 93 | 94 | end 95 | -------------------------------------------------------------------------------- /engine/server/network/host.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Host wrapper class for ENet 4 | -- 5 | --==========================================================================-- 6 | 7 | local enet = require( "enet" ) 8 | 9 | class( "host" ) 10 | 11 | function host:host( 12 | bind_address, 13 | peer_count, 14 | channel_count, 15 | in_bandwidth, 16 | out_bandwidth 17 | ) 18 | self._host = enet.host_create( 19 | bind_address, 20 | peer_count or 64, 21 | channel_count or 1, 22 | in_bandwidth or 0, 23 | out_bandwidth or 0 24 | ) 25 | end 26 | 27 | function host:isValid() 28 | return self._host ~= nil 29 | end 30 | 31 | function host:connect( address, channel_count, data ) 32 | return self._host:connect( address, channel_count, data ) 33 | end 34 | 35 | function host:service( timeout ) 36 | if ( timeout ) then 37 | return self._host:service( timeout ) 38 | else 39 | return self._host:service() 40 | end 41 | end 42 | 43 | function host:check_events() 44 | return self._host:check_events() 45 | end 46 | 47 | function host:compress_with_range_coder() 48 | return self._host:compress_with_range_coder() 49 | end 50 | 51 | function host:flush() 52 | return self._host:flush() 53 | end 54 | 55 | function host:broadcast( data, channel, flag ) 56 | if ( g_localhost_enet_peer ) then 57 | g_localhost_enet_peer:send( data, channel, flag ) 58 | end 59 | return self._host:broadcast( data, channel, flag ) 60 | end 61 | 62 | function host:channel_limit( limit ) 63 | return self._host:channel_limit( limit ) 64 | end 65 | 66 | function host:bandwidth_limit( incoming, outgoing ) 67 | return self._host:bandwidth_limit( incoming, outgoing ) 68 | end 69 | 70 | function host:total_sent_data() 71 | return self._host:total_sent_data() 72 | end 73 | 74 | function host:total_received_data() 75 | return self._host:total_received_data() 76 | end 77 | 78 | function host:service_time() 79 | return self._host:service_time() 80 | end 81 | 82 | function host:peer_count() 83 | return g_localhost_enet_peer and self._host:peer_count() + 1 or 84 | self._host:peer_count() 85 | end 86 | 87 | function host:get_peer( index ) 88 | if ( g_localhost_enet_peer and index == 1 ) then 89 | return g_localhost_enet_peer 90 | end 91 | return g_localhost_enet_peer and self._host:get_peer( index + 1 ) or 92 | self._host:get_peer( index ) 93 | end 94 | 95 | function host:get_socket_address() 96 | return self._host:get_socket_address() 97 | end 98 | 99 | function host:__tostring() 100 | return tostring( self.__host ) 101 | end 102 | -------------------------------------------------------------------------------- /engine/server/payloads.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Engine server payload handlers 4 | -- 5 | --==========================================================================-- 6 | 7 | local directoryWhitelist = { 8 | "maps" 9 | } 10 | 11 | local function onDownloadRequest( payload ) 12 | local filename = payload:get( "filename" ) 13 | if ( filename == nil ) then 14 | return 15 | end 16 | 17 | filename = string.fixslashes( filename ) 18 | local peer = payload:getPeer() 19 | 20 | print( tostring( peer ) .. " requested \"" .. filename .. "\"..." ) 21 | 22 | if ( string.find( filename, "/..", 1, true ) or 23 | string.ispathabsolute( filename ) ) then 24 | print( "Access denied to " .. tostring( peer ) .. "!" ) 25 | return 26 | end 27 | 28 | if ( love.filesystem.getInfo( filename ) == nil ) then 29 | print( filename .. " does not exist!" ) 30 | return 31 | end 32 | 33 | local directory = string.match( filename, "(.-)/" ) 34 | if ( directory == nil or 35 | not table.hasvalue( directoryWhitelist, directory ) ) then 36 | print( tostring( peer ) .. 37 | " requested file outside of directory whitelist (" .. 38 | directory .. ")!" ) 39 | return 40 | end 41 | 42 | engine.server.upload( filename, peer ) 43 | end 44 | 45 | payload.setHandler( onDownloadRequest, "download" ) 46 | 47 | local function sendEntity( player, entity ) 48 | if ( entity == player ) then 49 | return 50 | end 51 | 52 | local payload = payload( "entitySpawned" ) 53 | payload:set( "classname", entity:getClassname() ) 54 | payload:set( "entIndex", entity.entIndex ) 55 | payload:set( "networkVars", entity:getNetworkVarTypeLenValues() ) 56 | player:send( payload ) 57 | end 58 | 59 | local function sendEntities( player ) 60 | local entities = entity.getAll() 61 | for _, entity in ipairs( entities ) do 62 | sendEntity( player, entity ) 63 | end 64 | end 65 | 66 | local function onReceiveClientInfo( payload ) 67 | local player = payload:getPlayer() 68 | local graphicsWidth = payload:get( "graphicsWidth" ) 69 | local graphicsHeight = payload:get( "graphicsHeight" ) 70 | player:setGraphicsSize( graphicsWidth, graphicsHeight ) 71 | sendEntities( player ) 72 | player:initialSpawn() 73 | end 74 | 75 | payload.setHandler( onReceiveClientInfo, "clientInfo" ) 76 | 77 | local function onReceiveConcommand( payload ) 78 | local player = payload:getPlayer() 79 | local name = payload:get( "name" ) 80 | local argString = payload:get( "argString" ) 81 | if ( player == localplayer ) then 82 | return 83 | end 84 | 85 | concommand.dispatch( player, name, argString, argTable ) 86 | end 87 | 88 | payload.setHandler( onReceiveConcommand, "concommand" ) 89 | -------------------------------------------------------------------------------- /engine/shared/addon.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Addon interface 4 | -- 5 | --==========================================================================-- 6 | 7 | local hook = hook 8 | local love = love 9 | local print = print 10 | local require = require 11 | local string = string 12 | local table = table 13 | local ipairs = ipairs 14 | 15 | module( "addon" ) 16 | 17 | function load( arg ) 18 | local addons = love.filesystem.getDirectoryItems( "addons" ) 19 | for i = #addons, -1, 1 do 20 | local v = addons[ i ] 21 | local info = love.filesystem.getInfo( v ) 22 | local isDirectory = info and info.type == "directory" or false 23 | if ( string.fileextension( v ) ~= "zip" or not isDirectory ) then 24 | table.remove( addons, i ) 25 | end 26 | end 27 | 28 | for _, v in ipairs( addons ) do 29 | mount( v ) 30 | end 31 | end 32 | 33 | _addons = _addons or {} 34 | 35 | function getAddons() 36 | return _addons 37 | end 38 | 39 | function mount( addon ) 40 | if ( love.filesystem.mount( "addons/" .. addon, "" ) ) then 41 | table.insert( getAddons(), addon ) 42 | print( "Mounted \"" .. addon .. "\"!" ) 43 | require( "addons." .. addon ) 44 | hook.call( "shared", "onAddonMounted", addon ) 45 | return true 46 | end 47 | 48 | print( "Failed to mount \"" .. addon .. "\"!" ) 49 | return false 50 | end 51 | 52 | function unmount( addon ) 53 | local mounted = table.hasvalue( getAddons(), addon ) 54 | if ( not mounted ) then 55 | print( "Addon \"" .. addon .. "\" is not mounted!" ) 56 | return false 57 | end 58 | 59 | if ( love.filesystem.unmount( "addons/" .. addon ) ) then 60 | hook.call( "shared", "onAddonUnmounted", addon ) 61 | unrequire( "addons." .. addon ) 62 | print( "Unmounted \"" .. addon .. "\"!" ) 63 | return true 64 | end 65 | 66 | print( "Failed to unmount \"" .. addon .. "\"!" ) 67 | return false 68 | end 69 | -------------------------------------------------------------------------------- /engine/shared/buttons.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Input enumeration 4 | -- 5 | --==========================================================================-- 6 | 7 | _E = _E or {} 8 | _E.IN_FORWARD = bit.lshift( 1, 0 ) -- (1 << 0) 9 | _E.IN_BACK = bit.lshift( 1, 1 ) -- (1 << 1) 10 | _E.IN_LEFT = bit.lshift( 1, 2 ) -- (1 << 2) 11 | _E.IN_RIGHT = bit.lshift( 1, 3 ) -- (1 << 3) 12 | _E.IN_SPEED = bit.lshift( 1, 4 ) -- (1 << 4) 13 | _E.IN_USE = bit.lshift( 1, 5 ) -- (1 << 5) 14 | -------------------------------------------------------------------------------- /engine/shared/dblib.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Extends the debug library 4 | -- 5 | --==========================================================================-- 6 | 7 | require( "debug" ) 8 | 9 | function debug.getparameters( f ) 10 | local info = debug.getinfo( f, "u" ) 11 | local params = {} 12 | for i = 1, info.nparams do 13 | params[ i ] = debug.getlocal( f, i ) 14 | end 15 | return params, info.isvararg 16 | end 17 | -------------------------------------------------------------------------------- /engine/shared/entities/character.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Character class 4 | -- 5 | --==========================================================================-- 6 | 7 | entities.require( "entity" ) 8 | 9 | class "character" ( "entity" ) 10 | 11 | function character:character() 12 | entity.entity( self ) 13 | end 14 | 15 | function character:addTask( task, name ) 16 | if ( self._tasks == nil ) then 17 | self._tasks = {} 18 | end 19 | 20 | table.insert( self._tasks, { 21 | task = task, 22 | name = name 23 | } ) 24 | end 25 | 26 | function character:moveTo( position, callback ) 27 | if ( callback ) then 28 | callback() 29 | end 30 | end 31 | 32 | function character:nextTask() 33 | local tasks = self._tasks 34 | table.remove( tasks, 1 ) 35 | 36 | if ( #tasks == 0 ) then 37 | self:removeTasks() 38 | end 39 | end 40 | 41 | function character:removeTasks() 42 | self._tasks = nil 43 | end 44 | 45 | function character:tick( timestep ) 46 | self:updateTasks() 47 | entity.tick( self, timestep ) 48 | end 49 | 50 | function character:updateTasks() 51 | local tasks = self._tasks 52 | if ( tasks == nil ) then 53 | return 54 | end 55 | 56 | local task = tasks[ 1 ] 57 | if ( task and not task.running ) then 58 | task.running = true 59 | task.task( self, function() self:nextTask() end ) 60 | end 61 | end 62 | 63 | function character:__tostring() 64 | return "character: " .. self.__type 65 | end 66 | -------------------------------------------------------------------------------- /engine/shared/entities/networkvar.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Network Variable class 4 | -- 5 | --==========================================================================-- 6 | 7 | class( "networkvar" ) 8 | 9 | function networkvar:networkvar( entity, name ) 10 | self.entity = entity 11 | self.name = name 12 | end 13 | 14 | function networkvar:onValueChanged() 15 | local entity = self.entity 16 | if ( entity == nil ) then 17 | return 18 | end 19 | 20 | entity:onNetworkVarChanged( self ) 21 | end 22 | 23 | accessor( networkvar, "name" ) 24 | accessor( networkvar, "value" ) 25 | 26 | function networkvar:setValue( value ) 27 | local oldValue = self.value 28 | self.value = value 29 | if ( oldValue ~= value ) then 30 | self:onValueChanged() 31 | end 32 | end 33 | 34 | function networkvar:__tostring() 35 | local value = "\"" .. tostring( self.value ) .. "\"" 36 | if ( self.value == nil ) then 37 | value = "nil" 38 | end 39 | return "networkvar: " .. self.name .. " = " .. value 40 | end 41 | -------------------------------------------------------------------------------- /engine/shared/entities/npc.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: NPC class 4 | -- 5 | --==========================================================================-- 6 | 7 | entities.require( "character" ) 8 | 9 | class "npc" ( "character" ) 10 | 11 | function npc:npc() 12 | character.character( self ) 13 | 14 | self:networkNumber( "moveSpeed", 66 ) 15 | 16 | if ( _CLIENT ) then 17 | require( "engine.client.sprite" ) 18 | local sprite = sprite( "images.player" ) 19 | sprite:setFilter( "nearest", "nearest" ) 20 | self:setSprite( sprite ) 21 | end 22 | end 23 | 24 | function npc:spawn() 25 | entity.spawn( self ) 26 | 27 | local tileSize = game.tileSize 28 | local min = vector() 29 | local max = vector( tileSize, -tileSize ) 30 | self:initializePhysics( "dynamic" ) 31 | self:setCollisionBounds( min, max ) 32 | 33 | game.call( "shared", "onNPCSpawn", self ) 34 | end 35 | 36 | function npc:__tostring() 37 | return "npc: " .. self:getName() 38 | end 39 | -------------------------------------------------------------------------------- /engine/shared/entities/trigger.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Trigger class 4 | -- 5 | --==========================================================================-- 6 | 7 | entities.require( "entity" ) 8 | 9 | class "trigger" ( "entity" ) 10 | 11 | function trigger:trigger() 12 | entity.entity( self ) 13 | 14 | self:networkNumber( "width", 0 ) 15 | self:networkNumber( "height", 0 ) 16 | end 17 | 18 | function trigger:isVisibleToPlayer( player ) 19 | local minA, maxA = player:getGraphicsBounds() 20 | local width = self:getNetworkVar( "width" ) 21 | local height = self:getNetworkVar( "height" ) 22 | local minB = self:localToWorld( vector() ) 23 | local maxB = self:localToWorld( vector( width, -height ) ) 24 | return math.aabbsintersect( minA, maxA, minB, maxB ) 25 | end 26 | -------------------------------------------------------------------------------- /engine/shared/entities/trigger_changelevel.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: trigger_changelevel 4 | -- 5 | --==========================================================================-- 6 | 7 | entities.require( "trigger" ) 8 | 9 | class "trigger_changelevel" ( "trigger" ) 10 | 11 | function trigger_changelevel:trigger_changelevel() 12 | trigger.trigger( self ) 13 | end 14 | 15 | function trigger_changelevel:loadMap() 16 | local properties = self:getProperties() 17 | if ( properties == nil ) then 18 | return 19 | end 20 | 21 | local name = properties[ "map" ] 22 | if ( map.getByName( name ) ) then 23 | return 24 | end 25 | 26 | local worldIndex = map.findNextWorldIndex() 27 | map.load( name, nil, nil, worldIndex ) 28 | end 29 | 30 | function trigger_changelevel:removeMap() 31 | local properties = self:getProperties() 32 | if ( properties ) then 33 | local name = properties[ "map" ] 34 | local r = map.getByName( name ) 35 | if ( r ) then 36 | local players = player.getInOrNearMap( r ) 37 | if ( players == nil ) then 38 | map.unload( name ) 39 | end 40 | end 41 | end 42 | end 43 | 44 | function trigger_changelevel:tick( timestep ) 45 | for _, player in ipairs( player.getAll() ) do 46 | if ( self:isVisibleToPlayer( player ) ) then 47 | if ( not self.loaded ) then 48 | self:loadMap() 49 | self.loaded = true 50 | end 51 | else 52 | if ( self.loaded ) then 53 | self:removeMap() 54 | self.loaded = false 55 | end 56 | end 57 | end 58 | end 59 | 60 | entities.linkToClassname( trigger_changelevel, "trigger_changelevel" ) 61 | -------------------------------------------------------------------------------- /engine/shared/heaplib.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Binary heap library 4 | -- 5 | --==========================================================================-- 6 | 7 | class( "heap" ) 8 | 9 | local function getparentindex( i ) 10 | return i % 2 == 0 and i / 2 or ( i - 1 ) / 2 11 | end 12 | 13 | local function upheap( self, i ) 14 | if ( i <= 1 ) then 15 | return 16 | end 17 | 18 | local parent = getparentindex( i ) 19 | if ( self[ parent ] >= self[ i ] ) then 20 | self[ parent ], self[ i ] = self[ i ], self[ parent ] 21 | upheap( self, parent ) 22 | end 23 | end 24 | 25 | function heap:insert( value ) 26 | table.insert( self, value ) 27 | upheap( self, #self ) 28 | end 29 | 30 | local function getleftchildindex( i ) 31 | return 2 * i 32 | end 33 | 34 | local function getrightchildindex( i ) 35 | return 2 * i + 1 36 | end 37 | 38 | local function downheap( self, i ) 39 | local rindex = getrightchildindex( i ) 40 | local size = #self 41 | local lindex = getleftchildindex( i ) 42 | local min 43 | if ( rindex > size ) then 44 | if ( lindex > size ) then 45 | return 46 | else 47 | min = lindex 48 | end 49 | else 50 | if ( self[ lindex ] < self[ rindex ] ) then 51 | min = lindex 52 | else 53 | min = rindex 54 | end 55 | end 56 | if ( self[ i ] >= self[ min ] ) then 57 | self[ i ], self[ min ] = self[ min ], self[ i ] 58 | downheap( self, min ) 59 | end 60 | end 61 | 62 | function heap:remove( pos ) 63 | local size = #self 64 | self[ pos ] = self[ size ] 65 | self[ size ] = nil 66 | if ( size > 1 ) then 67 | downheap( self, 1 ) 68 | end 69 | end 70 | 71 | function heap:__tostring() 72 | local t = getmetatable( self ) 73 | setmetatable( self, {} ) 74 | local s = string.gsub( tostring( self ), "table", "heap" ) 75 | setmetatable( self, t ) 76 | return s 77 | end 78 | -------------------------------------------------------------------------------- /engine/shared/hook.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Hook interface 4 | -- 5 | --==========================================================================-- 6 | 7 | local pairs = pairs 8 | local pcall = pcall 9 | local print = print 10 | local table = table 11 | 12 | module( "hook" ) 13 | 14 | _hooks = _hooks or {} 15 | _hooks.client = _hooks.client or {} 16 | _hooks.server = _hooks.server or {} 17 | _hooks.shared = _hooks.shared or {} 18 | 19 | function call( universe, event, ... ) 20 | local eventHooks = _hooks[ universe ][ event ] 21 | if ( eventHooks == nil ) then 22 | return 23 | end 24 | 25 | for name, func in pairs( eventHooks ) do 26 | local v = { pcall( func, ... ) } 27 | if ( v[ 1 ] ) then 28 | if ( #v > 1 ) then 29 | table.remove( v, 1 ) 30 | return unpack( v ) 31 | end 32 | else 33 | print( "[hook \"" .. name .. "\" (" .. event .. ")]: " .. v[ 2 ] ) 34 | remove( universe, event, name ) 35 | end 36 | end 37 | end 38 | 39 | function set( universe, func, event, name ) 40 | universe = universe or "shared" 41 | _hooks[ universe ][ event ] = _hooks[ universe ][ event ] or {} 42 | _hooks[ universe ][ event ][ name ] = func 43 | end 44 | 45 | function remove( universe, event, name ) 46 | universe = universe or "shared" 47 | local eventHooks = _hooks[ universe ][ event ] 48 | if ( eventHooks and eventHooks[ name ] ) then 49 | eventHooks[ name ] = nil 50 | if ( table.len( eventHooks ) == 0 ) then 51 | _hooks[ universe ][ event ] = nil 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /engine/shared/loadlib.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Extends the package library 4 | -- 5 | --==========================================================================-- 6 | 7 | require( "package" ) 8 | 9 | if ( rawrequire == nil ) then 10 | rawrequire = require 11 | end 12 | 13 | local function getModuleFilename( modname ) 14 | local module = string.gsub( modname, "%.", "/" ) 15 | for _, paths in ipairs( { 16 | string.gmatch( package.path .. ";", "(.-);" ), 17 | string.gmatch( love.filesystem.getRequirePath() .. ";", "(.-);" ) 18 | } ) do 19 | for path in paths do 20 | path = string.gsub( path, "%.[\\/]", "" ) 21 | local filename = string.gsub( path, "?", module ) 22 | if ( path ~= "" and 23 | love.filesystem.getInfo( filename ) ~= nil ) then 24 | return filename 25 | end 26 | end 27 | end 28 | end 29 | 30 | function require( modname ) 31 | if ( package.watched[ modname ] ) then 32 | return rawrequire( modname ) 33 | end 34 | 35 | local status, ret = pcall( rawrequire, modname ) 36 | if ( status == false ) then 37 | error( ret, 2 ) 38 | end 39 | 40 | local filename = getModuleFilename( modname ) 41 | if ( filename ) then 42 | local modtime = love.filesystem.getInfo( filename ).modtime 43 | package.watched[ modname ] = modtime 44 | else 45 | package.watched[ modname ] = -1 46 | end 47 | 48 | -- print( "Loaded " .. modname ) 49 | return ret 50 | end 51 | 52 | local function unload( modname ) 53 | package.loaded[ modname ] = nil 54 | package.watched[ modname ] = nil 55 | end 56 | 57 | function unrequire( modname ) 58 | unload( modname ) 59 | -- print( "Unloaded " .. modname ) 60 | end 61 | 62 | package.watched = package.watched or {} 63 | 64 | local function reload( modname, filename ) 65 | unload( modname ) 66 | print( "Updating " .. modname .. "..." ) 67 | 68 | local status, err = pcall( require, modname ) 69 | if ( status == true ) then 70 | if ( game ) then 71 | game.call( "shared", "onReloadScript", modname ) 72 | else 73 | hook.call( "shared", "onReloadScript", modname ) 74 | end 75 | 76 | return 77 | end 78 | 79 | print( err ) 80 | 81 | local modtime = love.filesystem.getInfo( filename ).modtime 82 | package.watched[ modname ] = modtime 83 | end 84 | 85 | local function update( k, v ) 86 | if ( v == -1 ) then 87 | return 88 | end 89 | 90 | local filename = getModuleFilename( k ) 91 | if ( filename == nil ) then 92 | return 93 | end 94 | 95 | local modtime = love.filesystem.getInfo( filename ).modtime 96 | if ( modtime ~= v ) then 97 | reload( k, filename ) 98 | end 99 | end 100 | 101 | function package.update( dt ) 102 | for modname, modtime in pairs( package.watched ) do 103 | update( modname, modtime ) 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /engine/shared/map/tileset.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Map Tileset class 4 | -- 5 | --==========================================================================-- 6 | 7 | class( "map.tileset" ) 8 | 9 | local tileset = map.tileset 10 | 11 | function tileset:tileset( map, tilesetData ) 12 | self:setMap( map ) 13 | self.data = tilesetData 14 | self:parse() 15 | end 16 | 17 | accessor( tileset, "filename" ) 18 | accessor( tileset, "firstGid", nil, "firstgid" ) 19 | accessor( tileset, "image" ) 20 | accessor( tileset, "imageWidth", nil, "imagewidth" ) 21 | accessor( tileset, "imageHeight", nil, "imageheight" ) 22 | accessor( tileset, "name" ) 23 | accessor( tileset, "properties" ) 24 | accessor( tileset, "map" ) 25 | accessor( tileset, "spacing" ) 26 | accessor( tileset, "margin" ) 27 | accessor( tileset, "tileCount", nil, "tilecount" ) 28 | accessor( tileset, "tileOffset", nil, "tileoffset" ) 29 | accessor( tileset, "tiles" ) 30 | accessor( tileset, "tileWidth", nil, "tilewidth" ) 31 | accessor( tileset, "tileHeight", nil, "tileheight" ) 32 | 33 | function tileset:parse() 34 | if ( self.data == nil ) then 35 | return 36 | end 37 | 38 | local data = self.data 39 | self:setName( data[ "name" ] ) 40 | self:setFirstGid( data[ "firstgid" ] ) 41 | self:setFilename( data[ "filename" ] ) 42 | self:setTileWidth( data[ "tilewidth" ] ) 43 | self:setTileHeight( data[ "tileheight" ] ) 44 | self:setSpacing( data[ "spacing" ] ) 45 | self:setMargin( data[ "margin" ] ) 46 | if ( _CLIENT ) then 47 | local mapsDir = "maps/" 48 | local map = self:getMap() 49 | local path = string.stripfilename( map:getFilename() ) 50 | mapsDir = mapsDir .. path 51 | path = mapsDir .. data[ "image" ] 52 | path = string.stripdotdir( path ) 53 | self:setImage( path ) 54 | end 55 | self:setImageWidth( data[ "imagewidth" ] ) 56 | self:setImageHeight( data[ "imageheight" ] ) 57 | 58 | require( "common.vector" ) 59 | self:setTileOffset( vector.copy( data[ "tileoffset" ] ) ) 60 | 61 | self:setProperties( table.copy( data[ "properties" ] ) ) 62 | self:setTileCount( data[ "tilecount" ] ) 63 | self:setTiles( table.copy( data[ "tiles" ] ) ) 64 | 65 | self.data = nil 66 | end 67 | 68 | function tileset:setImage( image ) 69 | self.image = love.graphics.newImage( image ) 70 | self.image:setFilter( "nearest", "nearest" ) 71 | end 72 | 73 | function tileset:__tostring() 74 | return "tileset: \"" .. self:getName() .. "\"" 75 | end 76 | -------------------------------------------------------------------------------- /engine/shared/mathlib.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Extends the math library 4 | -- 5 | --==========================================================================-- 6 | 7 | require( "math" ) 8 | 9 | function math.aabbsintersect( minA, maxA, minB, maxB ) 10 | return minA.x <= maxB.x and 11 | maxA.x >= minB.x and 12 | maxA.y <= minB.y and 13 | minA.y >= maxB.y 14 | end 15 | 16 | math.fepsilon = 1e-5 17 | 18 | function math.approximately( a, b ) 19 | -- Calculate the difference. 20 | local diff = math.abs( a - b ) 21 | a = math.abs( a ) 22 | b = math.abs( b ) 23 | -- Find the largest 24 | local largest = ( b > a ) and b or a 25 | 26 | if ( diff <= largest * math.fepsilon ) then 27 | return true 28 | end 29 | 30 | return false 31 | end 32 | 33 | function math.clamp( n, l, u ) 34 | return n < l and l or ( n > u and u or n ) 35 | end 36 | 37 | function math.gcd( a, b ) 38 | local t = 0 39 | while b ~= 0 do 40 | t = b 41 | b = a % b 42 | a = t 43 | end 44 | return a 45 | end 46 | 47 | function math.lerp( f, t, dt ) 48 | return ( f + ( t - f ) * dt ) 49 | end 50 | 51 | function math.nearestmult( n, mult ) 52 | return math.round( n / mult ) * mult 53 | end 54 | 55 | function math.nearestpow2( n ) 56 | return 2 ^ math.ceil( math.log( n ) / math.log( 2 ) ) 57 | end 58 | 59 | math.phi = ( 1 + math.sqrt( 5 ) ) / 2 60 | 61 | function math.pointinrect( px, py, x, y, width, height ) 62 | return px >= x and 63 | py >= y and 64 | px < x + width and 65 | py < y + height 66 | end 67 | 68 | function math.pointonline( x1, y1, x2, y2, px, py ) 69 | local m = ( y2 - y1 ) / ( x2 - x1 ) 70 | local b = y1 - m * x1 71 | return py == m * px + b 72 | end 73 | 74 | function math.pointonlinesegment( x1, y1, x2, y2, px, py ) 75 | -- Test x out of bounds 76 | if ( x2 > x1 and px > x2 ) then 77 | return false 78 | elseif ( x2 < x1 and px < x2 ) then 79 | return false 80 | end 81 | 82 | -- Test y out of bounds 83 | if ( y2 > y1 and py > y2 ) then 84 | return false 85 | elseif ( y2 < y1 and py < y2 ) then 86 | return false 87 | end 88 | 89 | return math.pointonline( x1, y1, x2, y2, px, py ) 90 | end 91 | 92 | function math.remap(v, srcLow, srcHigh, destLow, destHigh) 93 | return destLow + (destHigh - destLow) * (v - srcLow) / (srcHigh - srcLow) 94 | end 95 | 96 | function math.remapClamp(v, srcLow, srcHigh, destLow, destHigh) 97 | return destLow + (destHigh - destLow) * math.clamp((v - srcLow) / (srcHigh - srcLow), 0, 1) 98 | end 99 | 100 | function math.round( n ) 101 | return math.floor( n + 0.5 ) 102 | end 103 | -------------------------------------------------------------------------------- /engine/shared/network/payload.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Payload class 4 | -- 5 | --==========================================================================-- 6 | 7 | require( "engine.shared.typelenvalues" ) 8 | 9 | class "payload" ( "typelenvalues" ) 10 | 11 | payload._handlers = payload._handlers or {} 12 | 13 | -- Generate ids for packet structures 14 | do 15 | local payloads = "engine.shared.network.payloads" 16 | if ( package.loaded[ payloads ] ) then 17 | unrequire( payloads ) 18 | end 19 | 20 | require( payloads ) 21 | 22 | typelenvalues.generateIds( payload.structs ) 23 | end 24 | 25 | function payload.initializeFromData( data ) 26 | local payload = payload() 27 | payload.data = data 28 | payload:deserialize() 29 | return payload 30 | end 31 | 32 | function payload.setHandler( func, struct ) 33 | payload._handlers[ struct ] = func 34 | end 35 | 36 | function payload:payload( struct ) 37 | typelenvalues.typelenvalues( self, payload.structs, struct ) 38 | end 39 | 40 | function payload:dispatchToHandler() 41 | local name = self:getStructName() 42 | if ( name == nil ) then 43 | return 44 | end 45 | 46 | local handler = payload._handlers[ name ] 47 | if ( handler ) then 48 | handler( self ) 49 | end 50 | end 51 | 52 | accessor( payload, "peer" ) 53 | 54 | function payload:getPlayer() 55 | return player.getByPeer( self.peer ) 56 | end 57 | 58 | function payload:sendToServer() 59 | if ( _CLIENT ) then 60 | local network = engine.client.network 61 | network.sendToServer( self ) 62 | end 63 | end 64 | 65 | function payload:broadcast() 66 | if ( _SERVER ) then 67 | local network = engine.server.network 68 | network.broadcast( self ) 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /engine/shared/path/node.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Pathfinding Node class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "node" ( "vector" ) 8 | 9 | function node:node( x, y, parent ) 10 | vector.vector( self, x, y ) 11 | self.parent = parent 12 | self.g = 0 13 | self.h = 0 14 | self.f = 0 15 | end 16 | 17 | accessor( node, "parent" ) 18 | 19 | node.__eq = vector.__eq 20 | 21 | function node.__lt( a, b ) 22 | return a.f < b.f 23 | end 24 | 25 | function node.__le( a, b ) 26 | return a.f <= b.f 27 | end 28 | 29 | function node:__tostring() 30 | return "node: (" .. self.x .. ", " .. self.y .. ")" 31 | end 32 | -------------------------------------------------------------------------------- /engine/shared/profile.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Profiling interface 4 | -- 5 | --==========================================================================-- 6 | 7 | require( "love.timer" ) 8 | 9 | local ipairs = ipairs 10 | local love = love 11 | local print = print 12 | local string = string 13 | local table = table 14 | local _G = _G 15 | 16 | module( "profile" ) 17 | 18 | _stack = _stack or {} 19 | _parent = _parent or _stack 20 | 21 | local function getChildren() 22 | -- `children` == `_stack` or budget. 23 | local children = _stack 24 | if ( _parent ~= _stack ) then 25 | _parent.children = _parent.children or {} 26 | children = _parent.children 27 | end 28 | return children 29 | end 30 | 31 | local function getBudget( children, name ) 32 | for _, budget in ipairs( children ) do 33 | if ( budget.name == name ) then 34 | return budget 35 | end 36 | end 37 | end 38 | 39 | function push( name ) 40 | local children = getChildren() 41 | local budget = getBudget( children, name ) 42 | if ( budget == nil ) then 43 | budget = { name = name, parent = _parent } 44 | table.insert( children, budget ) 45 | end 46 | 47 | _parent = budget 48 | budget.startTime = love.timer.getTime() 49 | end 50 | 51 | function pop( name ) 52 | _parent.endTime = love.timer.getTime() 53 | _parent.duration = _parent.endTime - _parent.startTime 54 | _parent = _parent.parent 55 | 56 | if ( _parent == nil ) then 57 | _parent = _stack 58 | end 59 | -- print( name .. " took " .. string.format( "%.3fms", 1000 * duration ) ) 60 | end 61 | -------------------------------------------------------------------------------- /engine/shared/tween.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Inbetweening interface 4 | -- 5 | --==========================================================================-- 6 | 7 | local math = math 8 | 9 | class( "tween" ) 10 | 11 | local pi = math.pi 12 | local cos = math.cos 13 | 14 | tween.easing = { 15 | linear = function( p ) return p end, 16 | swing = function( p ) return 0.5 - cos( p * pi ) / 2 end, 17 | easeOutQuint = function( x, t, b, c, d ) 18 | local temp = t / d - 1 19 | t = t / d - 1 20 | return c * ( ( temp ) * t * t * t * t + 1 ) + b 21 | end 22 | } 23 | 24 | function tween:tween( target, duration, vars ) 25 | self.target = target 26 | self.startTime = nil 27 | self.tweens = {} 28 | self.duration = duration or 0.4 29 | self.easing = vars.easing or "swing" 30 | self.onUpdate = vars.onUpdate 31 | self.onComplete = vars.onComplete 32 | 33 | vars.easing = nil 34 | vars.onUpdate = nil 35 | vars.onComplete = nil 36 | 37 | for member, value in pairs( vars ) do 38 | self.tweens[ member ] = { 39 | startValue = self.target[ member ], 40 | endValue = value, 41 | } 42 | end 43 | end 44 | 45 | local startTime = 0 46 | local duration = 0 47 | local remaining = 0 48 | local max = math.max 49 | local percent = 0 50 | local startValue = 0 51 | local endValue = 0 52 | local eased = 0 53 | local onComplete = nil 54 | 55 | function tween:update( dt ) 56 | if ( self.startTime == nil ) then 57 | self.startTime = love.timer.getTime() 58 | end 59 | 60 | startTime = self.startTime 61 | duration = self.duration 62 | remaining = max( 0, startTime + duration - love.timer.getTime() ) 63 | percent = 1 - ( remaining / duration or 0 ) 64 | self.pos = percent 65 | 66 | for member, t in pairs( self.tweens ) do 67 | startValue = t.startValue 68 | endValue = t.endValue 69 | eased = tween.easing[ self.easing ]( 70 | percent, duration * percent, 0, 1, duration 71 | ) 72 | self.target[ member ] = ( endValue - startValue ) * eased + startValue 73 | 74 | if ( self.onUpdate ) then 75 | self.onUpdate() 76 | end 77 | end 78 | 79 | if ( percent == 1 ) then 80 | onComplete = self.onComplete 81 | if ( onComplete ) then 82 | onComplete() 83 | end 84 | end 85 | end 86 | -------------------------------------------------------------------------------- /fonts/SourceCodePro-Light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/fonts/SourceCodePro-Light.otf -------------------------------------------------------------------------------- /fonts/SourceSansPro-Bold.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/fonts/SourceSansPro-Bold.otf -------------------------------------------------------------------------------- /fonts/SourceSansPro-Light.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/fonts/SourceSansPro-Light.otf -------------------------------------------------------------------------------- /fonts/SourceSansPro-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/fonts/SourceSansPro-Regular.otf -------------------------------------------------------------------------------- /game/client/gui/closedialog.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Close Dialog class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.closedialog" ( "gui.frame" ) 8 | 9 | local closedialog = gui.closedialog 10 | 11 | function closedialog:closedialog( parent, name ) 12 | gui.frame.frame( self, parent, "Close Dialog", "Quit Game" ) 13 | self.width = 546 14 | self.height = 203 15 | 16 | self:doModal() 17 | 18 | local text = "Are you sure you want to quit the game?" 19 | local label = gui.label( 20 | self, 21 | "Close Dialog Label", 22 | text 23 | ) 24 | label:setPos( 36, 86 ) 25 | local font = self:getScheme( "font" ) 26 | label:setWidth( font:getWidth( text ) ) 27 | 28 | local buttonYes = gui.button( self, "Close Dialog Yes Button", "Yes" ) 29 | buttonYes.height = nil 30 | buttonYes:setPadding( 14, 18, 13 ) 31 | buttonYes:setPos( 36, 86 + label:getHeight() + 18 ) 32 | buttonYes.onClick = function() 33 | love._shouldQuit = true 34 | love.quit() 35 | end 36 | 37 | local buttonNo = gui.button( self, "Close Dialog No Button", "No" ) 38 | buttonNo.height = nil 39 | buttonNo:setPadding( 14, 18, 13 ) 40 | buttonNo:setPos( 288, 86 + label:getHeight() + 18 ) 41 | buttonNo.onClick = function() 42 | self:close() 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /game/client/gui/hudabout.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: About HUD 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.hudabout" ( "gui.box" ) 8 | 9 | local hudabout = gui.hudabout 10 | 11 | function hudabout:hudabout( parent ) 12 | gui.box.box( self, parent, "HUD About" ) 13 | end 14 | -------------------------------------------------------------------------------- /game/client/gui/hudchattextbox.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Chat Text Box HUD 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.hudchattextbox" ( "gui.textbox" ) 8 | 9 | local hudchattextbox = gui.hudchattextbox 10 | 11 | function hudchattextbox:hudchattextbox( parent, name ) 12 | gui.textbox.textbox( self, parent, name, "" ) 13 | self.hudchat = parent 14 | 15 | self:setEditable( false ) 16 | self:setMultiline( true ) 17 | self:setScheme( "Chat" ) 18 | 19 | self.borderOpacity = 0 20 | self:setVisible( false ) 21 | end 22 | 23 | local CHAT_ANIM_TIME = 0.2 24 | 25 | function hudchattextbox:activate() 26 | if ( not self:isVisible() ) then 27 | self:setOpacity( 0 ) 28 | self:animate( { 29 | opacity = 1 30 | }, CHAT_ANIM_TIME, "easeOutQuint" ) 31 | end 32 | 33 | self:setVisible( true ) 34 | end 35 | 36 | function hudchattextbox:hide() 37 | if ( self.hiding ) then 38 | return 39 | end 40 | 41 | self.hiding = true 42 | 43 | self:animate( { 44 | opacity = 0, 45 | }, CHAT_ANIM_TIME, "easeOutQuint", function() 46 | self:setVisible( false ) 47 | self:setOpacity( 1 ) 48 | 49 | self.hiding = nil 50 | self.hideTime = nil 51 | end ) 52 | end 53 | 54 | function hudchattextbox:drawBorder() 55 | local width = self:getWidth() 56 | local height = self:getHeight() 57 | local color = color( self:getScheme( "textbox.borderColor" ) ) 58 | color[ 4 ] = color[ 4 ] * self.borderOpacity 59 | love.graphics.setColor( color ) 60 | local lineWidth = 1 61 | love.graphics.setLineWidth( lineWidth ) 62 | love.graphics.rectangle( 63 | "line", 64 | lineWidth / 2, 65 | lineWidth / 2, 66 | width - lineWidth, 67 | height - lineWidth 68 | ) 69 | end 70 | 71 | accessor( hudchattextbox, "hideTime" ) 72 | accessor( hudchattextbox, "hudchat" ) 73 | 74 | function hudchattextbox:invalidateLayout() 75 | local parent = self:getHudchat() 76 | local margin = 36 77 | local textboxHeight = 46 78 | local padding = 9 79 | local width = parent:getWidth() 80 | width = width - 2 * margin 81 | local height = parent:getHeight() 82 | height = height - textboxHeight - padding - 2 * margin 83 | self:setWidth( width ) 84 | self:setHeight( height ) 85 | 86 | gui.panel.invalidateLayout( self ) 87 | end 88 | 89 | function hudchattextbox:update( dt ) 90 | local parent = self:getHudchat() 91 | local hideTime = self:getHideTime() 92 | if ( hideTime and 93 | hideTime <= love.timer.getTime() and 94 | not parent:isVisible() ) then 95 | self:hide() 96 | end 97 | 98 | gui.textbox.update( self, dt ) 99 | end 100 | 101 | function hudchattextbox:updateCursor() 102 | local hudchat = self:getHudchat() 103 | if ( not hudchat:isVisible() ) then 104 | love.mouse.setCursor() 105 | return 106 | end 107 | 108 | gui.textbox.updateCursor( self ) 109 | end 110 | -------------------------------------------------------------------------------- /game/client/gui/huddialogue.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Dialogue HUD 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.huddialogue" ( "gui.hudframe" ) 8 | 9 | local huddialogue = gui.huddialogue 10 | 11 | function huddialogue:huddialogue( parent ) 12 | local name = "HUD Dialogue" 13 | gui.hudframe.hudframe( self, parent, name, "" ) 14 | self.width = 320 -- - 31 15 | self.height = 86 16 | 17 | local box = gui.box( self, name .. " Box" ) 18 | box:setWidth( self.width ) 19 | box:setHeight( self.height ) 20 | box:setPadding( 18 ) 21 | 22 | local header = gui.text( box, "h1" ) 23 | header:setDisplay( "block" ) 24 | header:setMarginBottom( 8 ) 25 | header:setFont( self:getScheme( "fontBold" ) ) 26 | 27 | local dialogue = gui.text( box, "p" ) 28 | dialogue:setDisplay( "block" ) 29 | 30 | self:invalidateLayout() 31 | self:activate() 32 | end 33 | 34 | function huddialogue:invalidateLayout() 35 | local x = love.graphics.getWidth() / 2 36 | x = x + 3 * game.tileSize 37 | local y = love.graphics.getHeight() / 2 38 | y = y - self:getHeight() / 2 39 | y = y - 2 * game.tileSize 40 | self:setPos( x, y ) 41 | gui.frame.invalidateLayout( self ) 42 | end 43 | -------------------------------------------------------------------------------- /game/client/gui/hudgamemenu/init.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Game Menu HUD 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.hudgamemenu" ( "gui.hudframe" ) 8 | 9 | local hudgamemenu = gui.hudgamemenu 10 | 11 | function hudgamemenu:hudgamemenu( parent ) 12 | local name = "HUD Game Menu" 13 | gui.hudframe.hudframe( self, parent, name, name ) 14 | self.width = 320 -- - 31 15 | self.height = 432 16 | 17 | require( "game.client.gui.hudgamemenu.navigation" ) 18 | require( "game.client.gui.hudgamemenu.navigationbutton" ) 19 | self.navigation = gui.hudgamemenunavigation( self ) 20 | self.navigation:setPos( 36, 86 ) 21 | self.navigation:setWidth( self.width - 2 * 36 ) 22 | self.navigation:setHeight( 31 ) 23 | 24 | require( "game.client.gui.hudgamemenu.inventory" ) 25 | self.inventory = gui.hudgamemenuinventory( self ) 26 | self.inventory:moveToBack() 27 | _G.g_Inventory = self.inventory 28 | 29 | require( "game.client.gui.hudgamemenu.stats" ) 30 | self.stats = gui.hudgamemenustats( self ) 31 | self.stats:moveToBack() 32 | self.stats:setVisible( false ) 33 | 34 | self:invalidateLayout() 35 | end 36 | 37 | function hudgamemenu:getTitle() 38 | if ( self.navigation == nil ) then 39 | return "Game Menu" 40 | end 41 | 42 | local item = self.navigation:getSelectedItem() 43 | return item:getText() 44 | end 45 | 46 | function hudgamemenu:invalidateLayout() 47 | local x = love.graphics.getWidth() - self:getWidth() - 18 48 | local y = love.graphics.getHeight() - self:getHeight() - 18 49 | self:setPos( x, y ) 50 | gui.frame.invalidateLayout( self ) 51 | end 52 | 53 | function hudgamemenu:onRemove() 54 | _G.g_Inventory = nil 55 | gui.panel.onRemove( self ) 56 | end 57 | 58 | concommand( "+gamemenu", "Opens the gamemenu", function() 59 | local visible = _G.g_GameMenu:isVisible() 60 | if ( not visible ) then 61 | _G.g_GameMenu:activate() 62 | end 63 | end, { "game" } ) 64 | 65 | concommand( "-gamemenu", "Closes the gamemenu", function() 66 | local visible = _G.g_GameMenu:isVisible() 67 | if ( visible ) then 68 | _G.g_GameMenu:close() 69 | end 70 | end, { "game" } ) 71 | 72 | local function onReloadScript() 73 | local gamemenu = g_GameMenu 74 | if ( gamemenu == nil ) then 75 | return 76 | end 77 | 78 | local visible = gamemenu:isVisible() 79 | gamemenu:remove() 80 | gamemenu = gui.hudgamemenu( g_Viewport ) 81 | g_GameMenu = gamemenu 82 | if ( visible ) then 83 | gamemenu:activate() 84 | end 85 | end 86 | 87 | onReloadScript() 88 | -------------------------------------------------------------------------------- /game/client/gui/hudgamemenu/inventory.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Game Menu Inventory class 4 | -- 5 | --==========================================================================-- 6 | 7 | require( "game.client.gui.hudgamemenu.itemgrid" ) 8 | 9 | class "gui.hudgamemenuinventory" ( "gui.box" ) 10 | 11 | local hudgamemenuinventory = gui.hudgamemenuinventory 12 | 13 | function hudgamemenuinventory:hudgamemenuinventory( parent ) 14 | gui.box.box( self, parent, "Inventory" ) 15 | self:setScheme( "Default" ) 16 | self:setDimensions( parent:getDimensions() ) 17 | 18 | self.grid = gui.itemgrid( self, "Inventory Item Grid" ) 19 | self.grid:setPos( 36, 86 + 31 + 18 ) 20 | self.grid:setDimensions( parent:getWidth() - 2 * 36, 314 ) 21 | self.grid:setColumns( 4 ) 22 | self.grid:setRows( 7 ) 23 | 24 | self:addInventoryHooks() 25 | end 26 | 27 | function hudgamemenuinventory:addInventoryHooks() 28 | local function onPlayerGotItem( player, item, count ) 29 | if ( player ~= localplayer ) then 30 | return 31 | end 32 | 33 | self.grid:addItem( item, count ) 34 | end 35 | 36 | hook.set( 37 | "shared", onPlayerGotItem, "onPlayerGotItem", "updateInventory" 38 | ) 39 | 40 | local function onPlayerRemovedItem( player, item, count ) 41 | if ( player ~= localplayer ) then 42 | return 43 | end 44 | 45 | self.grid:removeItem( item ) 46 | end 47 | 48 | hook.set( 49 | "shared", onPlayerRemovedItem, "onPlayerRemovedItem", "updateInventory" 50 | ) 51 | end 52 | 53 | function hudgamemenuinventory:removeInventoryHooks() 54 | hook.remove( "shared", "onPlayerGotItem", "updateInventory" ) 55 | hook.remove( "shared", "onPlayerRemovedItem", "updateInventory" ) 56 | end 57 | 58 | function hudgamemenuinventory:onRemove() 59 | self:removeInventoryHooks() 60 | gui.panel.onRemove( self ) 61 | end 62 | 63 | function hudgamemenuinventory:select( item ) 64 | item = self.grid:hasItem( item ) 65 | self:setSelectedItem( item ) 66 | end 67 | 68 | accessor( hudgamemenuinventory, "selectedItem" ) 69 | 70 | function hudgamemenuinventory:setSelectedItem( item ) 71 | if ( self.selectedItem ) then 72 | self.selectedItem:setSelected( false ) 73 | end 74 | 75 | if ( item ) then 76 | item:setSelected( true ) 77 | end 78 | 79 | self.selectedItem = item 80 | end 81 | 82 | function hudgamemenuinventory:use( item, value ) 83 | end 84 | -------------------------------------------------------------------------------- /game/client/gui/hudgamemenu/itemgrid.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Item Grid class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.itemgrid" ( "gui.box" ) 8 | 9 | local itemgrid = gui.itemgrid 10 | 11 | function itemgrid:itemgrid( parent, name ) 12 | gui.box.box( self, parent, name ) 13 | self:setDisplay( "block" ) 14 | self:setPosition( "absolute" ) 15 | 16 | self.items = {} 17 | self.columns = 0 18 | self.rows = 0 19 | end 20 | 21 | function itemgrid:addItem( item, count ) 22 | local itemdata = _G.item.getClass( item ).data 23 | local hasItem = self:hasItem( item ) 24 | if ( not itemdata.stackable or not hasItem ) then 25 | require( "game.client.gui.hudgamemenu.itembutton" ) 26 | local itembutton = gui.itembutton( self, item ) 27 | table.insert( self:getItems(), itembutton ) 28 | end 29 | 30 | self:invalidateLayout() 31 | end 32 | 33 | accessor( itemgrid, "columns" ) 34 | accessor( itemgrid, "rows" ) 35 | accessor( itemgrid, "items" ) 36 | 37 | function itemgrid:hasItem( item ) 38 | local items = self:getItems() 39 | for _, v in ipairs( items ) do 40 | if ( item == v:getItem() ) then 41 | return v 42 | end 43 | end 44 | return nil 45 | end 46 | 47 | function itemgrid:invalidateLayout() 48 | local items = self:getItems() 49 | local columns = self:getColumns() 50 | local rows = self:getRows() 51 | local columnGap = ( self:getWidth() - columns * 44 ) / ( columns - 1 ) 52 | local rowGap = ( self:getHeight() - rows * 44 ) / ( rows - 1 ) 53 | for n, v in pairs( items ) do 54 | n = n - 1 55 | local x = n % columns 56 | local y = math.floor( n / columns ) 57 | local xm = x * columnGap 58 | local ym = y * rowGap 59 | v:setPos( x * 44 + xm, y * 44 + ym ) 60 | end 61 | gui.panel.invalidateLayout( self ) 62 | end 63 | 64 | function itemgrid:removeItem( item ) 65 | local items = self:getItems() 66 | for i, v in ipairs( items ) do 67 | if ( item == v:getItem() ) then 68 | table.remove( items, i ) 69 | v:remove() 70 | break 71 | end 72 | end 73 | 74 | self:invalidateLayout() 75 | end 76 | -------------------------------------------------------------------------------- /game/client/gui/hudgamemenu/navigation.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Game Menu Navigation class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.hudgamemenunavigation" ( "gui.radiobuttongroup" ) 8 | 9 | local hudgamemenunavigation = gui.hudgamemenunavigation 10 | 11 | function hudgamemenunavigation:hudgamemenunavigation( parent ) 12 | gui.radiobuttongroup.radiobuttongroup( self, parent, "Game Menu Navigation" ) 13 | self:setScheme( "Default" ) 14 | 15 | self.tabs = { 16 | "Inventory", 17 | "Stats" 18 | } 19 | 20 | for _, name in ipairs( self.tabs ) do 21 | local item = gui.hudgamemenunavigationbutton( self, name ) 22 | self:addItem( item ) 23 | end 24 | end 25 | 26 | function hudgamemenunavigation:addItem( item, default ) 27 | item:setParent( self ) 28 | gui.radiobuttongroup.addItem( self, item ) 29 | 30 | if ( default or #self:getItems() == 1 ) then 31 | item:setDefault( true ) 32 | end 33 | 34 | self:invalidateLayout() 35 | end 36 | 37 | function hudgamemenunavigation:draw() 38 | gui.box.draw( self ) 39 | 40 | local property = "hudgamemenunavigation.backgroundColor" 41 | local lineWidth = 1 42 | local width = self:getWidth() 43 | love.graphics.setColor( self:getScheme( property ) ) 44 | love.graphics.setLineStyle( "rough" ) 45 | love.graphics.setLineWidth( lineWidth ) 46 | love.graphics.line( 47 | 0, lineWidth / 2, -- Top-left 48 | width, lineWidth / 2 -- Top-right 49 | ) 50 | end 51 | 52 | function hudgamemenunavigation:invalidateLayout() 53 | local listItems = self:getItems() 54 | if ( listItems ) then 55 | local x = 0 56 | for _, listItem in ipairs( listItems ) do 57 | listItem:setX( x ) 58 | x = x + listItem:getWidth() + 18 59 | end 60 | end 61 | gui.panel.invalidateLayout( self ) 62 | end 63 | 64 | function hudgamemenunavigation:onValueChanged( oldValue, newValue ) 65 | local parent = self:getParent() 66 | for _, name in ipairs( self.tabs ) do 67 | name = string.lower( name ) 68 | parent[ name ]:setVisible( false ) 69 | end 70 | 71 | if ( newValue ) then 72 | local panel = parent[ string.lower( newValue ) ] 73 | panel:setVisible( true ) 74 | panel:invalidateLayout() 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /game/client/gui/hudgamemenu/navigationbutton.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Game Menu Navigation Button class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.hudgamemenunavigationbutton" ( "gui.radiobutton" ) 8 | 9 | local hudgamemenunavigationbutton = gui.hudgamemenunavigationbutton 10 | 11 | function hudgamemenunavigationbutton:hudgamemenunavigationbutton( parent, name ) 12 | gui.radiobutton.radiobutton( self, parent, name, name ) 13 | self.width = nil 14 | self.height = nil 15 | self:setDisplay( "inline-block" ) 16 | self:setPosition( "static" ) 17 | self:setMarginRight( 18 ) 18 | self:setPaddingTop( 13 ) 19 | self:setValue( name ) 20 | end 21 | 22 | function hudgamemenunavigationbutton:draw() 23 | self:drawBorder() 24 | self:drawLabel() 25 | gui.box.draw( self ) 26 | end 27 | 28 | function hudgamemenunavigationbutton:drawBorder() 29 | if ( not self:isSelected() ) then 30 | return 31 | end 32 | 33 | local property = "hudgamemenunavigationbutton.borderColor" 34 | local lineWidth = 1 35 | local width = self:getWidth() 36 | love.graphics.setColor( self:getScheme( property ) ) 37 | love.graphics.setLineStyle( "rough" ) 38 | love.graphics.setLineWidth( lineWidth ) 39 | love.graphics.line( 40 | 0, lineWidth / 2, -- Top-left 41 | width, lineWidth / 2 -- Top-right 42 | ) 43 | end 44 | 45 | function hudgamemenunavigationbutton:drawLabel() 46 | if ( self:isDisabled() ) then 47 | self.text:setColor( self:getScheme( "radiobutton.disabled.textColor" ) ) 48 | elseif ( self:isSelected() ) then 49 | self.text:setColor( self:getScheme( "hudgamemenunavigationbutton.borderColor" ) ) 50 | elseif ( self.mouseover or self:isChildMousedOver() ) then 51 | self.text:setColor( self:getScheme( "hudgamemenunavigationbutton.mouseover.textColor" ) ) 52 | else 53 | self.text:setColor( self:getScheme( "hudgamemenunavigationbutton.textColor" ) ) 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /game/client/gui/hudhealth.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Health HUD 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.hudhealth" ( "gui.box" ) 8 | 9 | local hudhealth = gui.hudhealth 10 | 11 | function hudhealth:hudhealth( parent ) 12 | local name = "HUD Health" 13 | gui.box.box( self, parent, name ) 14 | self:setDisplay( "block" ) 15 | self:setPosition( "absolute" ) 16 | 17 | self.text = gui.text( self, "" ) 18 | self.text:setDisplay( "block" ) 19 | self.text:setColor( self:getScheme( "hudmoveindicator.textColor" ) ) 20 | self.text:setFont( self:getScheme( "entityFont" ) ) 21 | 22 | local label = gui.text( self, "Health" ) 23 | label:setColor( self:getScheme( "hudmoveindicator.smallTextColor" ) ) 24 | 25 | self:invalidateLayout() 26 | end 27 | 28 | function hudhealth:draw() 29 | self:drawHealth() 30 | 31 | gui.box.draw( self ) 32 | end 33 | 34 | function hudhealth:drawHealth() 35 | local health = localplayer:getNetworkVar( "health" ) 36 | self.text:set( health ) 37 | end 38 | 39 | function hudhealth:invalidateLayout() 40 | local margin = gui.scale( 96 ) 41 | local graphicsHeight = love.graphics.getHeight() 42 | local height = self:getHeight() 43 | self:setPos( margin, graphicsHeight - margin - height ) 44 | 45 | gui.panel.invalidateLayout( self ) 46 | end 47 | -------------------------------------------------------------------------------- /game/client/gui/hudmana.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Mana HUD 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.hudmana" ( "gui.box" ) 8 | 9 | local hudmana = gui.hudmana 10 | 11 | function hudmana:hudmana( parent ) 12 | local name = "HUD Mana" 13 | gui.box.box( self, parent, name ) 14 | self:setDisplay( "block" ) 15 | self:setPosition( "absolute" ) 16 | 17 | self.text = gui.text( self, "" ) 18 | self.text:setDisplay( "block" ) 19 | self.text:setColor( self:getScheme( "hudmoveindicator.textColor" ) ) 20 | self.text:setFont( self:getScheme( "entityFont" ) ) 21 | 22 | local label = gui.text( self, "Mana" ) 23 | label:setColor( self:getScheme( "hudmoveindicator.smallTextColor" ) ) 24 | 25 | self:invalidateLayout() 26 | end 27 | 28 | function hudmana:draw() 29 | self:drawMana() 30 | 31 | gui.box.draw( self ) 32 | end 33 | 34 | function hudmana:drawMana() 35 | local mana = localplayer:getNetworkVar( "mana" ) 36 | self.text:set( mana ) 37 | end 38 | 39 | function hudmana:invalidateLayout() 40 | local margin = gui.scale( 96 ) 41 | local graphicsHeight = love.graphics.getHeight() 42 | local height = self:getHeight() 43 | self:setPos( 44 | margin + g_HudHealth:getWidth() + margin / 2, 45 | graphicsHeight - margin - height 46 | ) 47 | 48 | gui.panel.invalidateLayout( self ) 49 | end 50 | -------------------------------------------------------------------------------- /game/client/gui/mainmenubutton.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Main Menu Button class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.mainmenubutton" ( "gui.button" ) 8 | 9 | local mainmenubutton = gui.mainmenubutton 10 | 11 | function mainmenubutton:mainmenubutton( parent, text ) 12 | local name = text and text or "Blank" .. " Button" 13 | gui.button.button( self, parent, name, text or "" ) 14 | self:setBorderWidth( 0 ) 15 | 16 | local font = self:getScheme( "mainmenuFont" ) 17 | self.text:set( text ) 18 | self.text:setFont( font ) 19 | 20 | self.height = font:getHeight() 21 | end 22 | 23 | function mainmenubutton:draw() 24 | local textColor = "mainmenubutton.dark.textColor" 25 | local mouseover = ( self.mouseover or self:isChildMousedOver() ) 26 | if ( self:isDisabled() ) then 27 | textColor = "mainmenubutton.dark.disabled.textColor" 28 | elseif ( self.mousedown and mouseover ) then 29 | textColor = "mainmenubutton.dark.mousedown.textColor" 30 | elseif ( self.mousedown or mouseover or self.focus ) then 31 | textColor = "mainmenubutton.dark.mouseover.textColor" 32 | end 33 | 34 | self.text:setColor( self:getScheme( textColor ) ) 35 | 36 | gui.box.draw( self ) 37 | end 38 | -------------------------------------------------------------------------------- /game/client/gui/mainmenuclosebutton.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Main Menu Close Button class 4 | -- 5 | --==========================================================================-- 6 | 7 | local gui = gui 8 | local love = love 9 | 10 | class "gui.mainmenuclosebutton" ( "gui.closebutton" ) 11 | 12 | local mainmenuclosebutton = gui.mainmenuclosebutton 13 | 14 | function mainmenuclosebutton:mainmenuclosebutton( parent ) 15 | gui.closebutton.closebutton( self, parent, "Main Menu Close Button" ) 16 | self.width = 32 17 | self.height = self.width + 1 18 | self.icon = self:getScheme( "icon" ) 19 | end 20 | 21 | function mainmenuclosebutton:draw() 22 | local iconColor = "mainmenuclosebutton.dark.iconColor" 23 | if ( self.mousedown and self.mouseover ) then 24 | iconColor = "mainmenuclosebutton.dark.mousedown.iconColor" 25 | elseif ( self.mousedown or self.mouseover ) then 26 | iconColor = "mainmenuclosebutton.dark.mouseover.iconColor" 27 | end 28 | 29 | local x = self:getWidth() / 2 - self.icon:getWidth() / 2 30 | local y = ( self:getHeight() - 1 ) / 2 - self.icon:getHeight() / 2 31 | love.graphics.setColor( self:getScheme( iconColor ) ) 32 | love.graphics.draw( self.icon, x, y ) 33 | 34 | gui.panel.draw( self ) 35 | end 36 | -------------------------------------------------------------------------------- /game/client/gui/optionsitem.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Options Item class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.optionsitem" ( "gui.dropdownlistitem" ) 8 | 9 | local optionsitem = gui.optionsitem 10 | 11 | function optionsitem:optionsitem( parent, name, text ) 12 | gui.dropdownlistitem.dropdownlistitem( self, parent, name, text .. " " ) 13 | self.entityText = gui.text( self, "" ) 14 | self.entityText:setFont( self:getScheme( "fontBold" ) ) 15 | end 16 | 17 | function optionsitem:drawText() 18 | local color = self:getScheme( "button.textColor" ) 19 | 20 | if ( self:isDisabled() ) then 21 | color = self:getScheme( "button.disabled.textColor" ) 22 | elseif ( self:isSelected() ) then 23 | color = self:getScheme( "dropdownlistitem.selected.textColor" ) 24 | elseif ( ( self.mouseover or self:isChildMousedOver() ) ) then 25 | color = self:getScheme( "dropdownlistitem.mouseover.textColor" ) 26 | end 27 | 28 | self.text:setColor( color ) 29 | self.entityText:setColor( color ) 30 | 31 | local entity = self:getEntity() 32 | local text = "" 33 | if ( type( entity ) == "string" ) then 34 | text = entity 35 | else 36 | text = entity and entity:getName() or "" 37 | end 38 | self.entityText:set( text ) 39 | end 40 | 41 | function optionsitem:setDefault( default ) 42 | end 43 | 44 | function optionsitem:setSelected( selected ) 45 | end 46 | 47 | accessor( optionsitem, "entity" ) 48 | -------------------------------------------------------------------------------- /game/client/gui/optionsitemgroup.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Options Item Group class 4 | -- 5 | --==========================================================================-- 6 | 7 | class "gui.optionsitemgroup" ( "gui.dropdownlistitemgroup" ) 8 | 9 | local optionsitemgroup = gui.optionsitemgroup 10 | 11 | function optionsitemgroup:optionsitemgroup( parent, name ) 12 | gui.dropdownlistitemgroup.dropdownlistitemgroup( self, parent, name ) 13 | -- UNDONE: The drop-down list field is reserved for the control responsible 14 | -- for the drop-down list item group. The control does not necessarily have 15 | -- to be a dropdownlist. 16 | -- self.dropDownList = nil 17 | 18 | -- self:setParent( parent ) 19 | end 20 | 21 | function optionsitemgroup:addItem( item ) 22 | item:setParent( self ) 23 | gui.radiobuttongroup.addItem( self, item ) 24 | 25 | item.onClick = function( item ) 26 | local value = item:getValue() 27 | value() 28 | end 29 | 30 | self:invalidateLayout() 31 | end 32 | 33 | function optionsitemgroup:invalidateLayout() 34 | self:updatePos() 35 | gui.panel.invalidateLayout( self ) 36 | end 37 | 38 | function optionsitemgroup:updatePos() 39 | local parent = self:getParent() 40 | local x, y = self:getPos() 41 | local width, height = self:getDimensions() 42 | local windowPadding = 4 43 | if ( x + width > parent:getWidth() ) then 44 | x = x - width 45 | end 46 | 47 | local overflow = y + height 48 | if ( overflow > parent:getHeight() - windowPadding ) then 49 | overflow = overflow - parent:getHeight() + windowPadding 50 | y = y - overflow 51 | end 52 | 53 | if ( y < windowPadding ) then 54 | y = windowPadding 55 | end 56 | 57 | self:setPos( x, y ) 58 | end 59 | -------------------------------------------------------------------------------- /game/client/init.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Game client interface 4 | -- 5 | --==========================================================================-- 6 | 7 | require( "engine.client.camera" ) 8 | 9 | local camera = camera 10 | local game = game 11 | local gui = gui 12 | local pcall = pcall 13 | local map = map 14 | local unrequire = unrequire 15 | local _G = _G 16 | 17 | module( "game.client" ) 18 | 19 | function createDefaultPanels() 20 | -- Initialize speech balloons 21 | local hudspeechballoons = gui.hudspeechballoons( _G.g_Viewport ) 22 | 23 | -- Initialize move indicator 24 | local hudmoveindicator = gui.hudmoveindicator( _G.g_Viewport ) 25 | _G.g_HudMoveIndicator = hudmoveindicator 26 | 27 | -- Initialize about 28 | local hudabout = gui.hudabout( _G.g_Viewport ) 29 | 30 | -- Initialize chat 31 | local chat = gui.hudchat( _G.g_Viewport ) 32 | _G.g_Chat = chat 33 | 34 | -- Initialize game menu 35 | local gamemenu = gui.hudgamemenu( _G.g_Viewport ) 36 | _G.g_GameMenu = gamemenu 37 | 38 | -- Initialize health 39 | local hudhealth = gui.hudhealth( _G.g_Viewport ) 40 | _G.g_HudHealth = hudhealth 41 | 42 | -- Initialize mana 43 | local hudmana = gui.hudmana( _G.g_Viewport ) 44 | _G.g_HudMana = hudmana 45 | 46 | -- Initialize dialogue 47 | -- local dialogue = gui.huddialogue( _G.g_Viewport ) 48 | -- _G.g_Dialogue = dialogue 49 | 50 | -- Initialize profiler 51 | local profiler = gui.hudprofiler( _G.g_Viewport ) 52 | _G.g_Profiler = profiler 53 | end 54 | 55 | function draw() 56 | if ( not _G.localplayer._initialized ) then 57 | return 58 | end 59 | 60 | -- Draw panels to worldspace 61 | gui.preDrawWorld() 62 | 63 | -- Draw world 64 | map.drawWorld() 65 | end 66 | 67 | function load( arg ) 68 | _G.g_Viewport = gui.viewport( _G.g_RootPanel ) 69 | _G.g_DebugOverlay = gui.debugoverlaypanel( _G.g_Viewport ) 70 | end 71 | 72 | function onMainMenuActivate() 73 | end 74 | 75 | function onMainMenuClose() 76 | end 77 | 78 | function onPlayerChat( player, message ) 79 | return true 80 | end 81 | 82 | function onReloadImage( filename ) 83 | end 84 | 85 | function onReloadSound( filename ) 86 | end 87 | 88 | function tick( timestep ) 89 | end 90 | 91 | function quit() 92 | _G.g_DebugOverlay:remove() 93 | _G.g_DebugOverlay = nil 94 | _G.g_Viewport:remove() 95 | _G.g_Viewport = nil 96 | end 97 | 98 | shutdown = quit 99 | 100 | function update( dt ) 101 | camera.update( dt ) 102 | end 103 | -------------------------------------------------------------------------------- /game/init.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: 4 | -- 5 | --==========================================================================-- 6 | 7 | require( "engine.shared.hook" ) 8 | 9 | _VADVENTURE = true 10 | 11 | local game = game or {} 12 | _G.game = game 13 | 14 | local hook = hook 15 | local _CLIENT = _CLIENT 16 | local _SERVER = _SERVER 17 | local _VADVENTURE = _VADVENTURE 18 | local _G = _G 19 | 20 | module( "game" ) 21 | 22 | tileSize = 16 23 | initialMap = "test_01" 24 | 25 | function conf( c ) 26 | c.title = "Vertex Adventure" 27 | c.author = "Planimeter" 28 | return c 29 | end 30 | 31 | function call( universe, event, ... ) 32 | local interface = game[ universe ] 33 | if ( universe == "shared" ) then 34 | interface = game 35 | end 36 | 37 | local values = { hook.call( universe, event, ... ) } 38 | if ( #values > 0 ) then 39 | return unpack( values ) 40 | end 41 | 42 | return interface[ event ]( ... ) 43 | end 44 | 45 | function onAddonMounted( addon ) 46 | end 47 | 48 | function onAddonUnmounted( addon ) 49 | end 50 | 51 | function onNPCSpawn( npc ) 52 | end 53 | 54 | function onPlayerConnect( player ) 55 | if ( _SERVER ) then 56 | _G.player.sendTextAll( player:getName() .. " has joined the game." ) 57 | end 58 | end 59 | 60 | function onPlayerDisconnect( player ) 61 | if ( _SERVER ) then 62 | _G.player.sendTextAll( player:getName() .. " has disconnected." ) 63 | end 64 | end 65 | 66 | if ( _VADVENTURE ) then 67 | function onPlayerGainedExperience( player, stat, xp ) 68 | end 69 | 70 | function onPlayerGotItem( player, item, count ) 71 | end 72 | 73 | function onPlayerRemovedItem( player, item, count ) 74 | end 75 | end 76 | 77 | function onPlayerInitialSpawn( player ) 78 | if ( _CLIENT and player == _G.localplayer ) then 79 | game.client.createDefaultPanels() 80 | end 81 | end 82 | 83 | if ( _VADVENTURE ) then 84 | function onPlayerLeveledUp( player, stat, level ) 85 | end 86 | end 87 | 88 | function onPlayerSpawn( player ) 89 | end 90 | 91 | function onReloadScript( modname ) 92 | end 93 | -------------------------------------------------------------------------------- /game/server/init.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Game server interface 4 | -- 5 | --==========================================================================-- 6 | 7 | local _G = _G 8 | 9 | module( "game.server" ) 10 | 11 | function getPlayerClass() 12 | -- Vertex Adventure's player class is `vaplayer`. In a class-based 13 | -- multiplayer game, you might want to change this. 14 | local entities = _G.entities 15 | entities.require( "vaplayer" ) 16 | 17 | -- Return the class 18 | local classmap = entities.getClassMap() 19 | return classmap[ "vaplayer" ] 20 | end 21 | 22 | function getSpawnPoint( player ) 23 | -- Find the first `prop_worldgate_spawn` in the player's current map 24 | local map = player:getMap() 25 | local entity = _G.entity 26 | local spawnPoints = entity.findByClassname( "prop_worldgate_spawn", map ) 27 | return spawnPoints and spawnPoints[ 1 ] or nil 28 | end 29 | 30 | function load( arg ) 31 | end 32 | 33 | function onPlayerConnect( player ) 34 | end 35 | 36 | function onPlayerSay( player, message ) 37 | return true 38 | end 39 | 40 | function onPlayerUse( player, entity, value ) 41 | return true 42 | end 43 | 44 | if ( _VADVENTURE ) then 45 | function onNPCTalkTo( npc, player, dialogue ) 46 | -- Check if the player is too far away 47 | local pos1 = npc:getPosition() 48 | local pos2 = player:getPosition() 49 | local dist = pos1 - pos2 50 | local tileSize = _G.game.tileSize 51 | if ( dist:lengthSqr() > tileSize * tileSize ) then 52 | player:sendText( "You can't reach that." ) 53 | return false 54 | end 55 | return true 56 | end 57 | end 58 | 59 | function tick( timestep ) 60 | end 61 | 62 | function quit() 63 | end 64 | 65 | shutdown = quit 66 | 67 | function update( dt ) 68 | end 69 | -------------------------------------------------------------------------------- /game/shared/entities/func_examine.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: func_examine 4 | -- 5 | --==========================================================================-- 6 | 7 | entities.require( "entity" ) 8 | require( "game" ) 9 | 10 | if ( _CLIENT ) then 11 | require( "engine.client.chat" ) 12 | end 13 | 14 | class "func_examine" ( "entity" ) 15 | 16 | function func_examine:func_examine() 17 | entity.entity( self ) 18 | end 19 | 20 | if ( _CLIENT ) then 21 | function func_examine:getOptions() 22 | return { 23 | { 24 | name = "Examine", 25 | value = function() self:examine() end 26 | } 27 | } 28 | end 29 | 30 | function func_examine:examine() 31 | local properties = self:getProperties() 32 | chat.addText( properties[ "text" ] ) 33 | end 34 | end 35 | 36 | function func_examine:spawn() 37 | entity.spawn( self ) 38 | 39 | local tileSize = game.tileSize 40 | local min = vector() 41 | local max = vector( tileSize, -tileSize ) 42 | self:initializePhysics() 43 | self:setCollisionBounds( min, max ) 44 | end 45 | 46 | entities.linkToClassname( func_examine, "func_examine" ) 47 | -------------------------------------------------------------------------------- /game/shared/entities/item.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: item 4 | -- 5 | --==========================================================================-- 6 | 7 | entities.require( "entity" ) 8 | -- require( "game" ) 9 | 10 | if ( _CLIENT ) then 11 | require( "engine.client.chat" ) 12 | end 13 | 14 | class "item" ( "entity" ) 15 | 16 | function item.getClass( classname ) 17 | entities.require( classname ) 18 | local classmap = entities.getClassMap() 19 | return classmap[ classname ] 20 | end 21 | 22 | item.data = { 23 | name = "Unknown Item", 24 | image = "images/error.png" 25 | } 26 | 27 | function item:item() 28 | entity.entity( self ) 29 | 30 | -- local tileSize = game.tileSize 31 | -- local min = vector() 32 | -- local max = vector( tileSize, -tileSize ) 33 | -- self:setCollisionBounds( min, max ) 34 | 35 | local name = self.data.name 36 | self:setNetworkVar( "name", name ) 37 | 38 | if ( _CLIENT ) then 39 | local filename = self.data.image 40 | local sprite = love.graphics.newImage( filename ) 41 | sprite:setFilter( "nearest", "nearest" ) 42 | self:setSprite( sprite ) 43 | end 44 | end 45 | 46 | if ( _CLIENT ) then 47 | function item:getOptions() 48 | return { 49 | { name = "Pickup", value = function() self:pickup() end }, 50 | { name = "Examine", value = function() self:examine() end } 51 | } 52 | end 53 | 54 | function item:getInventoryOptions() 55 | return { 56 | { 57 | name = "Use", 58 | value = function() 59 | g_Inventory:select( self.__type ) 60 | end 61 | }, 62 | { name = "Drop", value = function() self:drop() end }, 63 | { name = "Examine", value = function() self:examine() end } 64 | } 65 | end 66 | 67 | function item:pickup() 68 | localplayer:pickup( self ) 69 | end 70 | 71 | function item:drop() 72 | localplayer:drop( self.__type ) 73 | end 74 | 75 | function item:examine() 76 | chat.addText( "It's an item." ) 77 | end 78 | end 79 | 80 | function item:setCollisionBounds( min, max ) 81 | entity.setCollisionBounds( self, min, max ) 82 | 83 | local body = self:getBody() 84 | if ( body == nil ) then 85 | return 86 | end 87 | 88 | local fixtures = body:getFixtures() 89 | local fixture = fixtures[ 1 ] 90 | if ( fixture ) then 91 | fixture:setMask( 6 --[[ COLLISION_GROUP_PLAYER ]] ) 92 | end 93 | end 94 | 95 | function item:use( activator, value ) 96 | chat.addText( "Nothing interesting happens." ) 97 | end 98 | 99 | function item:useItem( activator, value ) 100 | chat.addText( "Nothing interesting happens." ) 101 | end 102 | -------------------------------------------------------------------------------- /game/shared/entities/item_apple.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: item_apple 4 | -- 5 | --==========================================================================-- 6 | 7 | require( "game.shared.entities.item" ) 8 | require( "game" ) 9 | 10 | class "item_apple" ( "item" ) 11 | 12 | item_apple.data = { 13 | name = "Apple", 14 | image = "images/entities/item_apple/1.png" 15 | } 16 | 17 | function item_apple:item_apple() 18 | item.item( self ) 19 | end 20 | 21 | if ( _CLIENT ) then 22 | function item_apple:getInventoryOptions() 23 | return { 24 | { name = "Eat", value = function() self:eat() end }, 25 | { 26 | name = "Use", 27 | value = function() 28 | g_Inventory:select( self.__type ) 29 | end 30 | }, 31 | { name = "Drop", value = function() item.drop( self ) end }, 32 | { name = "Examine", value = function() self:examine() end } 33 | } 34 | end 35 | 36 | function item_apple:examine() 37 | chat.addText( "Looks like an apple." ) 38 | end 39 | 40 | function item_apple:eat() 41 | localplayer:useItem( self.__type ) 42 | end 43 | end 44 | 45 | function item_apple:spawn() 46 | entity.spawn( self ) 47 | 48 | local tileSize = game.tileSize 49 | local min = vector() 50 | local max = vector( tileSize, -tileSize ) 51 | self:initializePhysics( "dynamic" ) 52 | self:setCollisionBounds( min, max ) 53 | 54 | local body = self:getBody() 55 | if ( body ) then 56 | body:setMass( 0.1496855 ) 57 | end 58 | end 59 | 60 | function item_apple:useItem( activator, value ) 61 | -- Give health 62 | local health = activator:getNetworkVar( "health" ) 63 | activator:setNetworkVar( "health", health + 3 ) 64 | 65 | -- Notify player 66 | activator:sendText( "The apple gives you some health." ) 67 | 68 | -- Remove from inventory 69 | activator:removeItem( self.__type ) 70 | end 71 | 72 | entities.linkToClassname( item_apple, "item_apple" ) 73 | -------------------------------------------------------------------------------- /game/shared/entities/item_gold.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: item_gold 4 | -- 5 | --==========================================================================-- 6 | 7 | require( "game.shared.entities.item" ) 8 | require( "game" ) 9 | 10 | class "item_gold" ( "item" ) 11 | 12 | item_gold.data = { 13 | name = "Gold", 14 | image = "images/entities/item_apple/1.png", 15 | stackable = true 16 | } 17 | 18 | function item_gold:item_gold() 19 | item.item( self ) 20 | end 21 | 22 | if ( _CLIENT ) then 23 | function item_gold:pickup() 24 | localplayer:pickup( self ) 25 | end 26 | 27 | function item_gold:drop() 28 | localplayer:drop( self.__type ) 29 | end 30 | 31 | function item_gold:examine() 32 | chat.addText( "Shiny." ) 33 | end 34 | end 35 | 36 | function item_gold:spawn() 37 | entity.spawn( self ) 38 | 39 | local tileSize = game.tileSize 40 | local min = vector() 41 | local max = vector( tileSize, -tileSize ) 42 | self:initializePhysics( "dynamic" ) 43 | self:setCollisionBounds( min, max ) 44 | end 45 | 46 | entities.linkToClassname( item_gold, "item_gold" ) 47 | -------------------------------------------------------------------------------- /game/shared/entities/prop_chest.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: prop_chest 4 | -- 5 | --==========================================================================-- 6 | 7 | entities.require( "entity" ) 8 | require( "game" ) 9 | 10 | if ( _CLIENT ) then 11 | require( "engine.client.chat" ) 12 | end 13 | 14 | class "prop_chest" ( "entity" ) 15 | 16 | function prop_chest:prop_chest() 17 | entity.entity( self ) 18 | 19 | self:setNetworkVar( "name", "Chest" ) 20 | 21 | if ( _CLIENT ) then 22 | local filename = "images/entities/prop_chest.png" 23 | local sprite = love.graphics.newImage( filename ) 24 | sprite:setFilter( "nearest", "nearest" ) 25 | self:setSprite( sprite ) 26 | end 27 | 28 | self.inventory = {} 29 | end 30 | 31 | if ( _CLIENT ) then 32 | function prop_chest:getOptions() 33 | return { 34 | { name = "Search", value = function() self:search() end }, 35 | { name = "Examine", value = function() self:examine() end } 36 | } 37 | end 38 | 39 | local function moveTo( position ) 40 | return function( character, next ) 41 | character:moveTo( position, next ) 42 | end 43 | end 44 | 45 | local function use( entity ) 46 | return function( player, next ) 47 | local payload = payload( "playerUse" ) 48 | payload:set( "entity", entity ) 49 | payload:set( "value", nil ) 50 | payload:sendToServer() 51 | 52 | next() 53 | end 54 | end 55 | 56 | function prop_chest:search() 57 | -- Stop everything 58 | localplayer:removeTasks() 59 | 60 | -- Walk to the front of the chest 61 | local position = self:getPosition() + vector( 0, game.tileSize ) 62 | localplayer:addTask( moveTo( position ) ) 63 | 64 | -- Use it 65 | localplayer:addTask( use( self ) ) 66 | end 67 | 68 | function prop_chest:examine() 69 | chat.addText( "I wonder what's inside." ) 70 | end 71 | end 72 | 73 | function prop_chest:spawn() 74 | entity.spawn( self ) 75 | 76 | local tileSize = game.tileSize 77 | local min = vector() 78 | local max = vector( tileSize, -tileSize ) 79 | self:initializePhysics() 80 | self:setCollisionBounds( min, max ) 81 | end 82 | 83 | function prop_chest:use( activator, value ) 84 | activator:sendText( "You search the chest but find nothing." ) 85 | end 86 | 87 | entities.linkToClassname( prop_chest, "prop_chest" ) 88 | -------------------------------------------------------------------------------- /game/shared/entities/prop_ore_rock.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: prop_ore_rock 4 | -- 5 | --==========================================================================-- 6 | 7 | entities.require( "entity" ) 8 | require( "game" ) 9 | 10 | if ( _CLIENT ) then 11 | require( "engine.client.chat" ) 12 | end 13 | 14 | class "prop_ore_rock" ( "entity" ) 15 | 16 | function prop_ore_rock:prop_ore_rock() 17 | entity.entity( self ) 18 | 19 | self:setNetworkVar( "name", "Ore Rock" ) 20 | 21 | if ( _CLIENT ) then 22 | local filename = "images/entities/prop_ore_rock.png" 23 | local sprite = love.graphics.newImage( filename ) 24 | sprite:setFilter( "nearest", "nearest" ) 25 | self:setSprite( sprite ) 26 | end 27 | 28 | self:setDrawShadow( false ) 29 | end 30 | 31 | if ( _CLIENT ) then 32 | function prop_ore_rock:getOptions() 33 | return { 34 | { name = "Pick", value = function() self:pick() end }, 35 | { name = "Examine", value = function() self:examine() end } 36 | } 37 | end 38 | 39 | function prop_ore_rock:pick() 40 | chat.addText( "Nothing interesting happens." ) 41 | end 42 | 43 | function prop_ore_rock:examine() 44 | chat.addText( "This rock contains ore." ) 45 | end 46 | end 47 | 48 | function prop_ore_rock:spawn() 49 | entity.spawn( self ) 50 | 51 | local tileSize = game.tileSize 52 | local min = vector() 53 | local max = vector( tileSize, -tileSize ) 54 | self:initializePhysics() 55 | self:setCollisionBounds( min, max ) 56 | end 57 | 58 | entities.linkToClassname( prop_ore_rock, "prop_ore_rock" ) 59 | -------------------------------------------------------------------------------- /game/shared/entities/prop_tree.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: prop_tree 4 | -- 5 | --==========================================================================-- 6 | 7 | entities.require( "entity" ) 8 | require( "game" ) 9 | 10 | if ( _CLIENT ) then 11 | require( "engine.client.chat" ) 12 | end 13 | 14 | class "prop_tree" ( "entity" ) 15 | 16 | function prop_tree:prop_tree() 17 | entity.entity( self ) 18 | 19 | self:setNetworkVar( "name", "Tree" ) 20 | 21 | if ( _CLIENT ) then 22 | local filename = "images/entities/prop_tree/1.png" 23 | local sprite = love.graphics.newImage( filename ) 24 | sprite:setFilter( "nearest", "nearest" ) 25 | self:setSprite( sprite ) 26 | end 27 | end 28 | 29 | if ( _CLIENT ) then 30 | function prop_tree:getOptions() 31 | return { 32 | { name = "Chop Down", value = function() self:chopDown() end }, 33 | { name = "Examine", value = function() self:examine() end } 34 | } 35 | end 36 | 37 | function prop_tree:chopDown() 38 | chat.addText( "Your hands alone really aren't going to cut it." ) 39 | end 40 | 41 | function prop_tree:examine() 42 | chat.addText( "Looks like a tree." ) 43 | end 44 | end 45 | 46 | function prop_tree:spawn() 47 | entity.spawn( self ) 48 | 49 | local tileSize = game.tileSize 50 | local min = vector() 51 | local max = vector( 2 * tileSize, -tileSize ) 52 | self:initializePhysics() 53 | self:setCollisionBounds( min, max ) 54 | end 55 | 56 | entities.linkToClassname( prop_tree, "prop_tree" ) 57 | -------------------------------------------------------------------------------- /game/shared/entities/prop_worldgate_spawn.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: prop_worldgate_spawn 4 | -- 5 | --==========================================================================-- 6 | 7 | entities.require( "entity" ) 8 | require( "game" ) 9 | 10 | if ( _CLIENT ) then 11 | require( "engine.client.chat" ) 12 | end 13 | 14 | class "prop_worldgate_spawn" ( "entity" ) 15 | 16 | function prop_worldgate_spawn:prop_worldgate_spawn() 17 | entity.entity( self ) 18 | 19 | if ( _CLIENT ) then 20 | local tileSize = game.tileSize 21 | self:setLocalPosition( vector( -tileSize, 0 ) ) 22 | end 23 | 24 | self:setNetworkVar( "name", "World Gate" ) 25 | 26 | if ( _CLIENT ) then 27 | local filename = "images/entities/prop_worldgate_spawn.png" 28 | local sprite = love.graphics.newImage( filename ) 29 | sprite:setFilter( "nearest", "nearest" ) 30 | self:setSprite( sprite ) 31 | end 32 | end 33 | 34 | if ( _CLIENT ) then 35 | function prop_worldgate_spawn:getOptions() 36 | return { 37 | { 38 | name = "Examine", 39 | value = function() self:examine() end 40 | } 41 | } 42 | end 43 | 44 | function prop_worldgate_spawn:examine() 45 | chat.addText( "Rather tall and ominous." ) 46 | end 47 | end 48 | 49 | function prop_worldgate_spawn:spawn() 50 | entity.spawn( self ) 51 | 52 | local tileSize = game.tileSize 53 | local min = vector() 54 | local max = vector( tileSize, -tileSize ) 55 | self:initializePhysics() 56 | self:setCollisionBounds( min, max ) 57 | end 58 | 59 | function prop_worldgate_spawn:tick( timestep ) 60 | local position = self:getPosition() 61 | local players = player.getAll() 62 | for _, player in ipairs( players ) do 63 | if ( player:getPosition() == position ) then 64 | player:moveTo( position + vector( 0, game.tileSize ) ) 65 | end 66 | end 67 | end 68 | 69 | entities.linkToClassname( prop_worldgate_spawn, "prop_worldgate_spawn" ) 70 | -------------------------------------------------------------------------------- /game/shared/entities/vanpc.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: vanpc 4 | -- 5 | --==========================================================================-- 6 | 7 | entities.require( "npc" ) 8 | 9 | if ( _CLIENT ) then 10 | require( "engine.client.chat" ) 11 | end 12 | 13 | class "vanpc" ( "npc" ) 14 | 15 | function vanpc:vanpc() 16 | npc.npc( self ) 17 | self:setNetworkVar( "name", "Example NPC" ) 18 | end 19 | 20 | if ( _CLIENT ) then 21 | function vanpc:getOptions() 22 | return { 23 | { name = "Talk to", value = function() self:talkTo() end }, 24 | { name = "Examine", value = function() self:examine() end } 25 | } 26 | end 27 | 28 | local function moveTo( position ) 29 | return function( character, next ) 30 | character:moveTo( position, next ) 31 | end 32 | end 33 | 34 | local function talkTo( entity ) 35 | return function( player, next ) 36 | local payload = payload( "npcTalkTo" ) 37 | payload:set( "npc", entity ) 38 | payload:set( "dialogue", nil ) 39 | payload:sendToServer() 40 | 41 | next() 42 | end 43 | end 44 | 45 | function vanpc:talkTo() 46 | -- Stop everything 47 | localplayer:removeTasks() 48 | 49 | -- Walk up to the NPC 50 | local pos1 = localplayer:getPosition() 51 | local pos2 = self:getPosition() 52 | local tiles = path.getPath( pos1, pos2 ) 53 | if ( tiles and #tiles > 1 ) then 54 | local nearestTile = tiles[ #tiles - 1 ] 55 | localplayer:addTask( moveTo( nearestTile ) ) 56 | end 57 | 58 | -- Talk to it 59 | localplayer:addTask( talkTo( self ) ) 60 | end 61 | 62 | function vanpc:examine() 63 | chat.addText( "Some guy." ) 64 | end 65 | end 66 | 67 | if ( _SERVER ) then 68 | local function onNPCTalkTo( payload ) 69 | local player = payload:getPlayer() 70 | local npc = payload:get( "npc" ) 71 | local dialogue = payload:get( "dialogue" ) 72 | 73 | local canTalkTo = game.call( 74 | "server", "onNPCTalkTo", npc, player, dialogue 75 | ) 76 | if ( canTalkTo == false ) then 77 | return 78 | end 79 | 80 | npc:onTalkTo( player, dialogue ) 81 | end 82 | 83 | payload.setHandler( onNPCTalkTo, "npcTalkTo" ) 84 | 85 | function vanpc:onTalkTo( vaplayer, dialogue ) 86 | local name = vaplayer:getName() 87 | vaplayer:sendText( "Hello, " .. name .. "!" ) 88 | end 89 | end 90 | 91 | vanpc.dialogue = vanpc.dialogue or {} 92 | local dialogue = vanpc.dialogue 93 | 94 | table.insert( dialogue, { 95 | index = 1, 96 | text = "Are you a boy? Or are you a girl?", 97 | options = { 2, 3 } 98 | } ) 99 | 100 | table.insert( dialogue, { 101 | index = 2, 102 | text = "Boy", 103 | next = 4 104 | } ) 105 | 106 | table.insert( dialogue, { 107 | index = 3, 108 | text = "Girl", 109 | next = 4 110 | } ) 111 | 112 | table.insert( dialogue, { 113 | index = 4, 114 | text = "Ah yes. We've been expecting you." 115 | } ) 116 | 117 | entities.linkToClassname( vanpc, "vanpc" ) 118 | -------------------------------------------------------------------------------- /game/shared/entities/weapon_bow.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: weapon_bow 4 | -- 5 | --==========================================================================-- 6 | 7 | require( "game.shared.entities.item" ) 8 | require( "game" ) 9 | 10 | class "weapon_bow" ( "item" ) 11 | 12 | weapon_bow.data = { 13 | name = "Bow", 14 | image = "images/entities/weapon_bow.png" 15 | } 16 | 17 | function weapon_bow:weapon_bow() 18 | item.item( self ) 19 | end 20 | 21 | if ( _CLIENT ) then 22 | function weapon_bow:pickup() 23 | localplayer:pickup( self ) 24 | end 25 | 26 | function weapon_bow:drop() 27 | localplayer:drop( self.__type ) 28 | end 29 | 30 | function weapon_bow:examine() 31 | chat.addText( "It's a bow. What else did you expect?" ) 32 | end 33 | end 34 | 35 | function weapon_bow:spawn() 36 | entity.spawn( self ) 37 | 38 | local tileSize = game.tileSize 39 | local min = vector() 40 | local max = vector( tileSize, -tileSize ) 41 | self:initializePhysics( "dynamic" ) 42 | self:setCollisionBounds( min, max ) 43 | 44 | local body = self:getBody() 45 | if ( body ) then 46 | body:setMass( 1.81437 ) 47 | end 48 | end 49 | 50 | entities.linkToClassname( weapon_bow, "weapon_bow" ) 51 | -------------------------------------------------------------------------------- /game/shared/entities/weapon_staff.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: weapon_staff 4 | -- 5 | --==========================================================================-- 6 | 7 | require( "game.shared.entities.item" ) 8 | require( "game" ) 9 | 10 | class "weapon_staff" ( "item" ) 11 | 12 | weapon_staff.data = { 13 | name = "Staff", 14 | image = "images/entities/weapon_staff.png" 15 | } 16 | 17 | function weapon_staff:weapon_staff() 18 | item.item( self ) 19 | end 20 | 21 | if ( _CLIENT ) then 22 | function weapon_staff:pickup() 23 | localplayer:pickup( self ) 24 | end 25 | 26 | function weapon_staff:drop() 27 | localplayer:drop( self.__type ) 28 | end 29 | 30 | function weapon_staff:examine() 31 | chat.addText( "Brown and sticky." ) 32 | end 33 | end 34 | 35 | function weapon_staff:spawn() 36 | entity.spawn( self ) 37 | 38 | local tileSize = game.tileSize 39 | local min = vector() 40 | local max = vector( tileSize, -tileSize ) 41 | self:initializePhysics( "dynamic" ) 42 | self:setCollisionBounds( min, max ) 43 | end 44 | 45 | entities.linkToClassname( weapon_staff, "weapon_staff" ) 46 | -------------------------------------------------------------------------------- /images/entities/item_apple/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/entities/item_apple/1.png -------------------------------------------------------------------------------- /images/entities/item_apple/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/entities/item_apple/2.png -------------------------------------------------------------------------------- /images/entities/prop_chest.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/entities/prop_chest.png -------------------------------------------------------------------------------- /images/entities/prop_ore_rock.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/entities/prop_ore_rock.png -------------------------------------------------------------------------------- /images/entities/prop_tree/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/entities/prop_tree/1.png -------------------------------------------------------------------------------- /images/entities/prop_worldgate_spawn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/entities/prop_worldgate_spawn.png -------------------------------------------------------------------------------- /images/entities/weapon_bow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/entities/weapon_bow.png -------------------------------------------------------------------------------- /images/entities/weapon_staff.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/entities/weapon_staff.png -------------------------------------------------------------------------------- /images/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/error.png -------------------------------------------------------------------------------- /images/gui/arrow_down.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/gui/arrow_down.png -------------------------------------------------------------------------------- /images/gui/arrow_down@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/gui/arrow_down@2x.png -------------------------------------------------------------------------------- /images/gui/check.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/gui/check.png -------------------------------------------------------------------------------- /images/gui/check@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/gui/check@2x.png -------------------------------------------------------------------------------- /images/gui/close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/gui/close.png -------------------------------------------------------------------------------- /images/gui/close@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/gui/close@2x.png -------------------------------------------------------------------------------- /images/gui/close_large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/gui/close_large.png -------------------------------------------------------------------------------- /images/gui/close_large@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/gui/close_large@2x.png -------------------------------------------------------------------------------- /images/gui/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/gui/logo.png -------------------------------------------------------------------------------- /images/gui/logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/gui/logo@2x.png -------------------------------------------------------------------------------- /images/gui/logo_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/gui/logo_dark.png -------------------------------------------------------------------------------- /images/gui/logo_dark@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/gui/logo_dark@2x.png -------------------------------------------------------------------------------- /images/gui/logo_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/gui/logo_small.png -------------------------------------------------------------------------------- /images/gui/logo_small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/gui/logo_small@2x.png -------------------------------------------------------------------------------- /images/gui/radiobutton_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/gui/radiobutton_foreground.png -------------------------------------------------------------------------------- /images/gui/radiobutton_foreground@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/gui/radiobutton_foreground@2x.png -------------------------------------------------------------------------------- /images/gui/selection_dot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/gui/selection_dot.png -------------------------------------------------------------------------------- /images/gui/selection_dot@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/gui/selection_dot@2x.png -------------------------------------------------------------------------------- /images/gui/throbber.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/gui/throbber.png -------------------------------------------------------------------------------- /images/gui/throbber@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/gui/throbber@2x.png -------------------------------------------------------------------------------- /images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/icon.png -------------------------------------------------------------------------------- /images/icon_osx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/icon_osx.png -------------------------------------------------------------------------------- /images/moveindicator.lua: -------------------------------------------------------------------------------- 1 | return { 2 | image = "images/moveindicator.png", 3 | width = 16, 4 | height = 16, 5 | frametime = 0.04, 6 | animations = { 7 | click = { 8 | from = 1, 9 | to = 8 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /images/moveindicator.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/moveindicator.png -------------------------------------------------------------------------------- /images/player.lua: -------------------------------------------------------------------------------- 1 | return { 2 | image = "images/player.png", 3 | width = 16, 4 | height = 32, 5 | frametime = 0.25, 6 | animations = { 7 | idlenorth = { 8 | from = 1, 9 | to = 1 10 | }, 11 | idleeast = { 12 | from = 2, 13 | to = 2 14 | }, 15 | idlesouth = { 16 | from = 3, 17 | to = 3 18 | }, 19 | idlewest = { 20 | from = 4, 21 | to = 4 22 | }, 23 | -- idlenorth 24 | idlenortheast = { 25 | from = 1, 26 | to = 1, 27 | }, 28 | -- idlesouth 29 | idlesoutheast = { 30 | from = 3, 31 | to = 3, 32 | }, 33 | -- idlesouth 34 | idlesouthwest = { 35 | from = 3, 36 | to = 3, 37 | }, 38 | -- idlenorth 39 | idlenorthwest = { 40 | from = 1, 41 | to = 1, 42 | }, 43 | walknorth = { 44 | from = 33, 45 | to = 36 46 | }, 47 | walkeast = { 48 | from = 65, 49 | to = 68, 50 | }, 51 | walksouth = { 52 | from = 97, 53 | to = 100, 54 | }, 55 | walkwest = { 56 | from = 129, 57 | to = 132, 58 | }, 59 | walknortheast = { 60 | from = 161, 61 | to = 164, 62 | }, 63 | walksoutheast = { 64 | from = 193, 65 | to = 196, 66 | }, 67 | walksouthwest = { 68 | from = 225, 69 | to = 228, 70 | }, 71 | walknorthwest = { 72 | from = 257, 73 | to = 260, 74 | } 75 | }, 76 | events = { 77 | -- walknorth 78 | [ 33 ] = "rightfootstep", 79 | [ 35 ] = "leftfootstep", 80 | -- walkeast 81 | [ 65 ] = "rightfootstep", 82 | [ 67 ] = "leftfootstep", 83 | -- walksouth 84 | [ 96 ] = "rightfootstep", 85 | [ 98 ] = "leftfootstep", 86 | -- walkwest 87 | [ 129 ] = "leftfootstep", 88 | [ 131 ] = "rightfootstep", 89 | -- walknortheast 90 | [ 161 ] = "rightfootstep", 91 | [ 163 ] = "leftfootstep", 92 | -- walksoutheast 93 | [ 193 ] = "rightfootstep", 94 | [ 195 ] = "leftfootstep", 95 | -- walksouthwest 96 | [ 224 ] = "leftfootstep", 97 | [ 226 ] = "rightfootstep", 98 | -- walknorthwest 99 | [ 257 ] = "leftfootstep", 100 | [ 259 ] = "rightfootstep" 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /images/player.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/player.png -------------------------------------------------------------------------------- /images/tilesets/architect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/tilesets/architect.png -------------------------------------------------------------------------------- /images/tilesets/developer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/tilesets/developer.png -------------------------------------------------------------------------------- /images/tilesets/world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/images/tilesets/world.png -------------------------------------------------------------------------------- /init.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Default engine entry point 4 | -- 5 | --==========================================================================-- 6 | 7 | assert( false, "attempt to load game from init.lua" ) 8 | -------------------------------------------------------------------------------- /main.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Engine entry point 4 | -- 5 | --==========================================================================-- 6 | 7 | require( "engine" ) 8 | -------------------------------------------------------------------------------- /maps/Architect.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /maps/Developer.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /maps/World.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /schemes/Chat.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Chat HUD scheme 4 | -- 5 | --==========================================================================-- 6 | 7 | local chat = scheme( "Chat" ) 8 | 9 | chat.textbox = { 10 | backgroundColor = color( 0, 0, 0, 0 * 255 ), 11 | borderColor = color( 104, 106, 107, 0.66 * 255 ), 12 | textColor = color( 230, 230, 230, 255 ), 13 | 14 | mouseover = { 15 | borderColor = color( 163, 126, 71, 0.42 * 255 ), 16 | textColor = color( 163, 167, 168, 255 ) 17 | }, 18 | 19 | focus = { 20 | borderColor = color( 163, 126, 71, 0.27 * 255 ), 21 | textColor = color( 163, 167, 168, 255 ) 22 | } 23 | } 24 | 25 | -- NOTE: The following arguments to `newFont` are undocumented. 26 | local dpiscale = love.graphics.getDPIScale() 27 | local r_window_highdpi = convar.getConvar( "r_window_highdpi" ) 28 | if ( r_window_highdpi:getNumber() == 2 ) then 29 | dpiscale = 1 30 | end 31 | 32 | chat.font = love.graphics.newFont( 33 | "fonts/SourceSansPro-Regular.otf", 14, "normal", dpiscale 34 | ) 35 | -------------------------------------------------------------------------------- /schemes/Console.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Console scheme 4 | -- 5 | --==========================================================================-- 6 | 7 | local console = scheme( "Console" ) 8 | 9 | console.textbox = { 10 | backgroundColor = color( 35, 35, 36, 0.66 * 255 ), 11 | borderColor = color( 104, 106, 107, 0.66 * 255 ), 12 | textColor = color( 104, 106, 107, 255 ), 13 | 14 | mouseover = { 15 | borderColor = color( 163, 126, 71, 0.42 * 255 ), 16 | textColor = color( 163, 167, 168, 255 ) 17 | }, 18 | 19 | focus = { 20 | borderColor = color( 163, 126, 71, 0.27 * 255 ), 21 | textColor = color( 163, 167, 168, 255 ) 22 | } 23 | } 24 | 25 | -- NOTE: The following arguments to `newFont` are undocumented. 26 | local dpiscale = love.graphics.getDPIScale() 27 | local r_window_highdpi = convar.getConvar( "r_window_highdpi" ) 28 | if ( r_window_highdpi:getNumber() == 2 ) then 29 | dpiscale = 1 30 | end 31 | 32 | console.font = love.graphics.newFont( 33 | "fonts/SourceCodePro-Light.otf", 12, "normal", dpiscale 34 | ) 35 | -------------------------------------------------------------------------------- /scripts/test/box.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Test box 4 | -- 5 | --==========================================================================-- 6 | 7 | local e = gui.createElement 8 | 9 | local name = "Quit Game" 10 | 11 | class "gui.boxtestframe" ( "gui.frame" ) 12 | 13 | local boxtestframe = gui.boxtestframe 14 | 15 | function boxtestframe:boxtestframe( parent, name, title ) 16 | gui.frame.frame( self, parent, name, title ) 17 | 18 | local child = e( "box", { 19 | parent = self, 20 | position = "absolute", 21 | y = 86, 22 | paddingTop = 0, 23 | padding = 36 24 | }, { 25 | e( "text", { 26 | marginBottom = 9, 27 | text = "Are you sure you want to quit the game?", 28 | -- color = color.white 29 | } ), 30 | e( "box", { 31 | display = "block" 32 | }, { 33 | e( "button", { 34 | display = "inline", 35 | position = "static", 36 | -- width = "nil", 37 | -- height = "nil", 38 | padding = 14, 39 | marginRight = 36, 40 | text = "Quit", 41 | onClick = function() love._shouldQuit = true; love.quit() end 42 | } ), 43 | e( "button", { 44 | display = "inline", 45 | position = "static", 46 | -- width = "nil", 47 | -- height = "nil", 48 | padding = 14, 49 | text = "Cancel", 50 | onClick = function() self:close() end 51 | } ) 52 | } ) 53 | } ) 54 | 55 | self:setWidth( child:getWidth() ) 56 | self:setHeight( 86 + child:getHeight() ) 57 | end 58 | 59 | local frame = gui.boxtestframe( nil, name, name ) 60 | frame:setRemoveOnClose( true ) 61 | frame:moveToCenter() 62 | frame:activate() 63 | -------------------------------------------------------------------------------- /scripts/test/line.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Test line drawing 4 | -- 5 | --==========================================================================-- 6 | 7 | local name = "Line Drawing Test" 8 | local frame = gui.frame( nil, name, name ) 9 | 10 | function frame:draw() 11 | gui.frame.draw( self ) 12 | 13 | love.graphics.setColor( color.red ) 14 | local lineWidth = 2 15 | love.graphics.setLineStyle( "rough" ) 16 | love.graphics.setLineWidth( lineWidth ) 17 | 18 | -- Horizontal Line 19 | -- love.graphics.line( 20 | -- lineWidth / 2, lineWidth / 2, -- Top-left 21 | -- self:getWidth(), lineWidth / 2 -- Top-right 22 | -- ) 23 | 24 | -- Vertical Line 25 | -- love.graphics.line( 26 | -- self:getWidth() - lineWidth / 2, 0 -- Top-right 27 | -- self:getWidth() - lineWidth / 2, self:getHeight() -- Bottom-right 28 | -- ) 29 | 30 | -- Three-point line 31 | love.graphics.line( 32 | lineWidth / 2, lineWidth / 2, -- Top-left 33 | self:getWidth() - lineWidth / 2, lineWidth / 2, -- Top-right 34 | self:getWidth() - lineWidth / 2, self:getHeight() -- Bottom-right 35 | ) 36 | end 37 | 38 | frame:setRemoveOnClose( true ) 39 | frame:moveToCenter() 40 | frame:activate() 41 | -------------------------------------------------------------------------------- /scripts/test/textbox.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Test text box 4 | -- 5 | --==========================================================================-- 6 | 7 | local name = "Text Box Test" 8 | 9 | class "gui.textboxtestframe" ( "gui.frame" ) 10 | 11 | local textboxtestframe = gui.textboxtestframe 12 | 13 | function textboxtestframe:textboxtestframe( parent, name, title ) 14 | gui.frame.frame( self, parent, name, title ) 15 | 16 | local textbox = gui.textbox( self ) 17 | 18 | local margin = 36 19 | local minHeight = self:getMinHeight() 20 | minHeight = minHeight + textbox:getHeight() 21 | self:setMinHeight( minHeight + margin ) 22 | 23 | local x = margin 24 | local y = 86 -- Title Bar Height 25 | local width = self:getWidth() 26 | local height = self:getHeight() 27 | width = width - 2 * margin 28 | height = height - y - margin 29 | textbox:setPos( x, y ) 30 | textbox:setWidth( width ) 31 | textbox:setHeight( height ) 32 | textbox:setMultiline( true ) 33 | self.textbox = textbox 34 | 35 | self:invalidateLayout() 36 | end 37 | 38 | function textboxtestframe:invalidateLayout() 39 | local textbox = self.textbox 40 | local margin = 36 41 | local y = 86 -- Title Bar Height 42 | local width = self:getWidth() 43 | local height = self:getHeight() 44 | width = width - 2 * margin 45 | height = height - y - margin 46 | textbox:setWidth( width ) 47 | textbox:setHeight( height ) 48 | 49 | gui.frame.invalidateLayout( self ) 50 | end 51 | 52 | local frame = gui.textboxtestframe( nil, name, name ) 53 | frame:setRemoveOnClose( true ) 54 | frame:moveToCenter() 55 | frame:activate() 56 | -------------------------------------------------------------------------------- /shaders/coloroverlay.frag: -------------------------------------------------------------------------------- 1 | //=========== Copyright © 2019, Planimeter, All rights reserved. =============// 2 | // 3 | // Purpose: 4 | // 5 | //============================================================================// 6 | 7 | vec4 effect( vec4 color, Image tex, vec2 texcoord, vec2 pixcoord ) 8 | { 9 | return vec4( color.rgb, Texel( tex, texcoord ).a ); 10 | } 11 | -------------------------------------------------------------------------------- /shaders/gaussianblur.frag: -------------------------------------------------------------------------------- 1 | //=========== Copyright © 2019, Planimeter, All rights reserved. =============// 2 | // 3 | // Purpose: Gaussian blur fragment reference shader 4 | // https://developer.nvidia.com/gpugems/GPUGems3/gpugems3_ch40.html 5 | // 6 | //============================================================================// 7 | 8 | uniform float sigma; // Gaussian sigma 9 | uniform vec2 dir; // horiz=(1.0, 0.0), vert=(0.0, 1.0) 10 | uniform int support; // int(sigma * 3.0) truncation 11 | vec4 effect( vec4 color, Image tex, vec2 texcoord, vec2 pixcoord ) 12 | { 13 | vec2 loc = texcoord; // center pixel cooordinate 14 | vec4 acc = vec4( 0.0f ); // accumulator 15 | float norm = 0.0f; 16 | for (int i = -support; i <= support; i++) { 17 | float coeff = exp(-0.5 * float(i) * float(i) / (sigma * sigma)); 18 | acc += (Texel(tex, loc + float(i) * dir)) * coeff; 19 | norm += coeff; 20 | } 21 | acc *= 1/norm; // normalize for unity gain 22 | return acc; 23 | } 24 | -------------------------------------------------------------------------------- /shaders/gaussianblur.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Gaussian blur fragment shader 4 | -- 5 | --==========================================================================-- 6 | 7 | require( "shaders.shader" ) 8 | 9 | class "gaussianblur" ( "shader" ) 10 | 11 | local gaussianblur = shader._shaders[ "gaussianblur" ] or gaussianblur 12 | 13 | function gaussianblur:gaussianblur() 14 | local width, height = love.graphics.getDimensions() 15 | self.scale = 1 / 2 16 | width = width * self.scale 17 | height = height * self.scale 18 | self.horizontalPass = love.graphics.newCanvas( width, height, { dpiscale = 1 } ) 19 | self.verticalPass = love.graphics.newCanvas( width, height, { dpiscale = 1 } ) 20 | -- local fragmentShader = love.filesystem.read( "shaders/gaussianblur.frag" ) 21 | -- self.shader = love.graphics.newShader( fragmentShader ) 22 | end 23 | 24 | function gaussianblur:renderTo( func ) 25 | local shader = love.graphics.getShader() 26 | love.graphics.setShader( self.shader ) 27 | 28 | local width, height = love.graphics.getDimensions() 29 | width = width * self.scale 30 | height = height * self.scale 31 | self.shader:send( "dir", { 1 / width, 0 } ) 32 | self.horizontalPass:renderTo( function() 33 | love.graphics.push() 34 | love.graphics.scale( self.scale, self.scale ) 35 | func() 36 | love.graphics.pop() 37 | end ) 38 | 39 | local b = love.graphics.getBlendMode() 40 | love.graphics.setBlendMode( "alpha", "premultiplied" ) 41 | 42 | self.shader:send( "dir", { 0, 1 / height } ) 43 | self.verticalPass:renderTo( function() 44 | love.graphics.clear() 45 | love.graphics.draw( self.horizontalPass ) 46 | end ) 47 | 48 | love.graphics.setBlendMode( b ) 49 | love.graphics.setShader( shader ) 50 | end 51 | 52 | function gaussianblur:draw() 53 | love.graphics.setColor( color.white ) 54 | love.graphics.draw( self.verticalPass, 0, 0, 0, 1 / self.scale ) 55 | end 56 | 57 | function gaussianblur:set( key, value ) 58 | if ( key == "sigma" ) then 59 | -- self.shader:send( "sigma", value ) 60 | -- self.shader:send( "norm", 1/(math.sqrt(2*math.pi)*value) ) 61 | -- self.shader:send( "support", (value * 3.0) ) 62 | self:generateShader( value, (value * 3.0) ) 63 | end 64 | 65 | return self 66 | end 67 | 68 | function gaussianblur:generateShader( sigma, support ) 69 | -- See `shaders/gaussianblur.frag` 70 | -- Loop unroll Gaussian convolution 71 | local norm = 0 72 | local forLoop = {} 73 | local line = "acc += (Texel(tex, loc + %.1f * dir)) * %f;" 74 | for i = -support, support do 75 | local coeff = math.exp(-0.5 * i * i / (sigma * sigma)); 76 | table.insert( forLoop, ( norm > 0 and "\t" or "" ) .. 77 | string.format( line, i, coeff ) 78 | ) 79 | norm = norm + coeff; 80 | end 81 | table.insert( forLoop, "\tacc *= 1/" .. norm .. ";\r\n" ) 82 | 83 | local fragmentShader = [[ 84 | uniform vec2 dir; 85 | vec4 effect( vec4 color, Image tex, vec2 texcoord, vec2 pixcoord ) 86 | { 87 | vec2 loc = texcoord; 88 | vec4 acc = vec4( 0.0 ); 89 | ]] .. table.concat( forLoop, "\r\n" ) .. [[ 90 | return acc; 91 | } 92 | ]] 93 | self.shader = love.graphics.newShader( fragmentShader ) 94 | end 95 | 96 | shader.register( gaussianblur, "gaussianblur" ) 97 | -------------------------------------------------------------------------------- /shaders/shader.lua: -------------------------------------------------------------------------------- 1 | --=========== Copyright © 2019, Planimeter, All rights reserved. ===========-- 2 | -- 3 | -- Purpose: Shader class 4 | -- 5 | --==========================================================================-- 6 | 7 | class( "shader" ) 8 | 9 | shader._shaders = shader._shaders or {} 10 | 11 | function shader.getShader( name, width, height ) 12 | return shader._shaders[ name ]( width, height ) 13 | end 14 | 15 | function shader.register( class, name ) 16 | shader._shaders[ name ] = class 17 | getfenv( 2 )[ name ] = nil 18 | end 19 | 20 | function shader:shader( width, height ) 21 | end 22 | 23 | function shader:renderTo( func ) 24 | end 25 | 26 | function shader:draw() 27 | end 28 | 29 | function shader:set( key, value ) 30 | end 31 | 32 | function shader:__tostring() 33 | local t = getmetatable( self ) 34 | setmetatable( self, {} ) 35 | local s = string.gsub( tostring( self ), "table", "shader" ) 36 | setmetatable( self, t ) 37 | return s 38 | end 39 | -------------------------------------------------------------------------------- /shaders/stroke.frag: -------------------------------------------------------------------------------- 1 | //=========== Copyright © 2019, Planimeter, All rights reserved. =============// 2 | // 3 | // Purpose: 4 | // 5 | //============================================================================// 6 | 7 | uniform vec2 resolution; 8 | uniform float width; 9 | 10 | const int samples = 20; 11 | const float pi = 3.1415926535898f; 12 | 13 | vec4 effect( vec4 color, Image tex, vec2 texcoord, vec2 pixcoord ) 14 | { 15 | // Stroke 16 | float alpha = 0.0f; 17 | float angle = 0.0f; 18 | for ( int i = 0; i < samples; i++ ) 19 | { 20 | angle += 1.0f / ( float( samples ) / 2.0f ) * pi; 21 | 22 | float x = ( width / resolution.x ) * cos( angle ); 23 | float y = ( width / resolution.y ) * sin( angle ); 24 | vec2 offset = vec2( x, y ); 25 | 26 | float sampleAlpha = Texel( tex, texcoord + offset ).a; 27 | alpha = max( alpha, sampleAlpha ); 28 | } 29 | 30 | // Texture 31 | vec4 FragColor = color * alpha; 32 | vec4 texel = Texel( tex, texcoord ); 33 | FragColor = mix( FragColor, texel, texel.a ); 34 | return FragColor; 35 | } 36 | -------------------------------------------------------------------------------- /sounds/footsteps/grassleft.lua: -------------------------------------------------------------------------------- 1 | return { 2 | source = "sounds/footsteps/grassleft.wav", 3 | volume = 0.25 4 | } 5 | -------------------------------------------------------------------------------- /sounds/footsteps/grassleft.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/sounds/footsteps/grassleft.wav -------------------------------------------------------------------------------- /sounds/footsteps/grassright.lua: -------------------------------------------------------------------------------- 1 | return { 2 | source = "sounds/footsteps/grassright.wav", 3 | volume = 0.25 4 | } 5 | -------------------------------------------------------------------------------- /sounds/footsteps/grassright.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Planimeter/game-engine-2d/e8fc46be351dd75a7849d6e9e40fdca59f687c80/sounds/footsteps/grassright.wav --------------------------------------------------------------------------------