diff --git a/.gitignore b/.gitignore index 6641b4cd..58a69a86 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# macOS +*.DS_Store + # IntelliJ Project Files .idea/* *.iml diff --git a/CHANGELOG.md b/CHANGELOG.md index 486c4a33..8695a259 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,33 @@ +# Version 0.10.0.1089 - 2017-10-28 + +## Additions +- Added ammo indicator which displays the total amount of ammunition a character has in his inventory +- Added basics for parcel based procedural map generation + - Map layouts determine the general look of the map such as road placement and where parcels are placed + - Building-prefabs can be spawned in parcels matching their size + - Prefabs can be rotated randomly + - Spawn randomly generated foliage parcels +- Added completely revamped inventory user interface + - General groundwork for future user interface additions + +## Removals +- Removed unused resource file +- Removed heal all selector on base screen + +## Fixes +- Fixed crash on base screen when health screen was opened before a character was selected + +## Other Changes +- Character names are aligned to the left now +- Improved interaction between mouse and keyboard controls in menu screens +- Nationalities are now picked from a weighted list to control the rarity of certain nations +- The aim overlay will always mark unseen tiles as potentially blocking +- Changed minimum resolution from 800x600 to 1024x768 +- Improved the ingame help screen and updated it to follow the general UI style + + + + # Version 0.9.2.1079 - 2017-10-28 ## Fixes @@ -188,7 +218,7 @@ # Version 0.5.0.725 - 2017-02-09 ## Additions -- Added a new smaller map with more tactical possibilites +- Added a new smaller map with more tactical possibilities - Added new tiles "Gravel" and "Wooden Floor" - Added new world object "Tree" - Added preliminary item descriptions @@ -403,8 +433,8 @@ - FOV isn't drawn for AI controlled factions - Tweaked shot calculations - Uses the maximum angle for a shot's derivation correctly now - - Randomly varies the projectile's travelling distance -- Improved line of sight drawning + - Randomly varies the projectile's traveling distance +- Improved line of sight drawing - Line of sight is now generated in real time between the active character and the mouse cursor - Center the camera on characters which have been selected via right-clicks - Use different sounds based on the selected weapon type diff --git a/README.md b/README.md index be18fca7..8c56056a 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # On The Roadside -[![Version](https://img.shields.io/badge/Version-0.9.2.1079-blue.svg)](https://github.com/rm-code/on-the-roadside/releases/latest) +[![Version](https://img.shields.io/badge/Version-0.10.0.1089-blue.svg)](https://github.com/rm-code/on-the-roadside/releases/latest) [![LOVE](https://img.shields.io/badge/L%C3%96VE-0.10.2-EA316E.svg)](http://love2d.org/) [![Build Status](https://travis-ci.com/rm-code/On-The-Roadside.svg?token=q3rLXeyGTBN9VB2zsWMr&branch=develop)](https://travis-ci.com/rm-code/On-The-Roadside) diff --git a/conf.lua b/conf.lua index 3df749d3..20a14955 100644 --- a/conf.lua +++ b/conf.lua @@ -1,56 +1,56 @@ -local PROJECT_TITLE = "On The Roadside"; +local PROJECT_TITLE = "On The Roadside" -local PROJECT_IDENTITY = "rmcode_otr"; +local PROJECT_IDENTITY = "rmcode_otr" -local PROJECT_VERSION = require( 'version' ); +local PROJECT_VERSION = require( 'version' ) -local LOVE_VERSION = "0.10.2"; +local LOVE_VERSION = "0.10.2" --- -- Initialise löve's config file. -- @param t -- function love.conf(t) - t.identity = PROJECT_IDENTITY; - t.version = LOVE_VERSION; - t.console = true; + t.identity = PROJECT_IDENTITY + t.version = LOVE_VERSION + t.console = true - t.accelerometerjoystick = true; - t.gammacorrect = false; + t.accelerometerjoystick = true + t.gammacorrect = false - t.window.title = PROJECT_TITLE; - t.window.icon = nil; - t.window.width = 800; - t.window.height = 600; - t.window.borderless = false; - t.window.resizable = true; - t.window.minwidth = 800; - t.window.minheight = 600; - t.window.fullscreen = true; - t.window.fullscreentype = "desktop"; - t.window.vsync = true; - t.window.msaa = 0; - t.window.display = 1; - t.window.highdpi = false; - t.window.x = nil; - t.window.y = nil; + t.window.title = PROJECT_TITLE + t.window.icon = nil + t.window.width = 0 + t.window.height = 0 + t.window.borderless = false + t.window.resizable = true + t.window.minwidth = 1024 + t.window.minheight = 768 + t.window.fullscreen = true + t.window.fullscreentype = "desktop" + t.window.vsync = true + t.window.msaa = 0 + t.window.display = 1 + t.window.highdpi = false + t.window.x = nil + t.window.y = nil - t.modules.audio = true; - t.modules.event = true; - t.modules.graphics = true; - t.modules.image = true; - t.modules.joystick = true; - t.modules.keyboard = true; - t.modules.math = true; - t.modules.mouse = true; - t.modules.physics = true; - t.modules.sound = true; - t.modules.system = true; - t.modules.timer = true; - t.modules.touch = true; - t.modules.video = true; - t.modules.window = true; - t.modules.thread = true; + t.modules.audio = true + t.modules.event = true + t.modules.graphics = true + t.modules.image = true + t.modules.joystick = true + t.modules.keyboard = true + t.modules.math = true + t.modules.mouse = true + t.modules.physics = true + t.modules.sound = true + t.modules.system = true + t.modules.timer = true + t.modules.touch = true + t.modules.video = true + t.modules.window = true + t.modules.thread = true end --- @@ -58,7 +58,7 @@ end -- function getVersion() if PROJECT_VERSION then - return PROJECT_VERSION; + return PROJECT_VERSION end end @@ -67,6 +67,6 @@ end -- function getTitle() if PROJECT_TITLE then - return PROJECT_TITLE; + return PROJECT_TITLE end end diff --git a/lib/TGFParser.lua b/lib/TGFParser.lua index 5429bacc..8d38f79e 100644 --- a/lib/TGFParser.lua +++ b/lib/TGFParser.lua @@ -55,12 +55,24 @@ local function loadFile( path ) -- Change the target table once the '#' separator is reached. -- TODO love.filesystem.lines is currently bugged on Mac OS 10.13 -- therefore we have to use a quick workaround - local str = love.filesystem.read(path) - for line in str:gmatch("[^\r\n]+") do - if line == '#' then - target = edges; - else - target[#target + 1] = line; + + if love then + local str = love.filesystem.read(path) + for line in str:gmatch("[^\r\n]+") do + if line == '#' then + target = edges; + else + target[#target + 1] = line; + end + end + else + -- Change the target table once the '#' separator is reached. + for line in io.lines( path ) do + if line == '#' then + target = edges; + else + target[#target + 1] = line; + end end end diff --git a/main.lua b/main.lua index f6999ea4..9b177026 100644 --- a/main.lua +++ b/main.lua @@ -1,6 +1,7 @@ local ScreenManager = require('lib.screenmanager.ScreenManager'); local ProFi = require( 'lib.ProFi' ); local Log = require( 'src.util.Log' ); +local DebugGrid = require( 'src.ui.overlays.DebugGrid' ) -- ------------------------------------------------ -- Local Variables @@ -10,13 +11,36 @@ local Log = require( 'src.util.Log' ); local profile = 0; local info; +local debugGrid + +-- ------------------------------------------------ +-- Constants +-- ------------------------------------------------ + +local DEBUG_OUTPUT_FLAG = '-d' +local DEBUG_GRID_FLAG = '-g' +local DEBUG_FULLSCREEN_FLAG = '-f' +local DEBUG_WINDOWED_FLAG = '-w' + -- ------------------------------------------------ -- Callbacks -- ------------------------------------------------ -function love.load() +function love.load( args ) Log.init(); + for _, arg in pairs( args ) do + if arg == DEBUG_OUTPUT_FLAG then + Log.setDebugActive( true ) + elseif arg == DEBUG_GRID_FLAG then + debugGrid = true + elseif arg == DEBUG_FULLSCREEN_FLAG then + love.window.setFullscreen( true ) + elseif arg == DEBUG_WINDOWED_FLAG then + love.window.setFullscreen( false ) + end + end + info = {}; info[#info + 1] = "==================="; info[#info + 1] = string.format( "Title: '%s'", getTitle() ); @@ -66,6 +90,10 @@ function love.draw() ProFi:writeReport( string.format( '../profiling/draw_%d.txt', os.time( os.date( '*t' )))); profile = 0; end + + if debugGrid then + DebugGrid.draw() + end end function love.update(dt) @@ -116,6 +144,10 @@ function love.mousemoved( x, y, dx, dy, isTouch ) ScreenManager.mousemoved( x, y, dx, dy, isTouch ); end +function love.wheelmoved( dx, dy ) + ScreenManager.wheelmoved( dx, dy ) +end + function love.errhand( msg ) msg = tostring( msg ); @@ -168,15 +200,14 @@ function love.errhand( msg ) end end + table.insert(err, '\n\nYou can find the error in the latest.log file in your save directory.' ) + table.insert(err, 'Press to open the directoy. Press to close the game.' ) + local p = table.concat(err, "\n") p = string.gsub(p, "\t", "") p = string.gsub(p, "%[string \"(.-)\"%]", "%1") - -- Open save directory where the error log is saved. - Log.error( 'You can find the error in the latest.log file in your save directory. Opening save directory now ...' ); - love.system.openURL( 'file://' .. love.filesystem.getSaveDirectory() ) - local function draw() local pos = love.window.toPixels(70) love.graphics.clear(love.graphics.getBackgroundColor()) @@ -189,8 +220,13 @@ function love.errhand( msg ) for e, a, _, _ in love.event.poll() do if e == "quit" then return - elseif e == "keypressed" and a == "escape" then - return + elseif e == "keypressed" then + if a == "return" then + love.system.openURL( 'file://' .. love.filesystem.getSaveDirectory() ) + return + elseif a == "escape" then + return + end elseif e == "touchpressed" then local name = love.window.getTitle() if #name == 0 or name == "Untitled" then name = "Game" end diff --git a/publish-release.sh b/publish-release.sh index 37e14cc6..ef463d47 100644 --- a/publish-release.sh +++ b/publish-release.sh @@ -77,8 +77,15 @@ mv -i -v OTR_$formatted.app ../OTR_$formatted-OSX.app cd .. rm -r LOVE_OSX +## ZIP THE LOVE FILE +# Fix for https://github.com/itchio/butler/issues/58#issuecomment-299619964 +zip OTR_$formatted-LOVE.zip OTR_$formatted.love + +# Remove original love file. +rm OTR_$formatted.love + # Publish to itch.io echo "Publishing to itch.io" butler push OTR_$formatted-WIN.zip rmcode/on-the-roadside:win --userversion $major.$minor.$patch.$build butler push OTR_$formatted-OSX.app rmcode/on-the-roadside:osx --userversion $major.$minor.$patch.$build -butler push OTR_$formatted.love rmcode/on-the-roadside:win-osx-linux --userversion $major.$minor.$patch.$build +butler push OTR_$formatted-LOVE.zip rmcode/on-the-roadside:win-osx-linux --userversion $major.$minor.$patch.$build diff --git a/res/data/creatures/names/names.lua b/res/data/creatures/names/names.lua deleted file mode 100644 index 31ca6343..00000000 --- a/res/data/creatures/names/names.lua +++ /dev/null @@ -1,1209 +0,0 @@ -return { - german = { - "Albrecht", - "Arnold", - "Bauer", - "Baumann", - "Beck", - "Becker", - "Berger", - "Bergmann", - "Brandt", - "Braun", - "Busch", - "Böhm", - "Dietrich", - "Engel", - "Fischer", - "Frank", - "Franke", - "Friedrich", - "Fuchs", - "Graf", - "Groß", - "Günther", - "Haas", - "Hahn", - "Hartmann", - "Heinrich", - "Herrmann", - "Hoffmann", - "Hofmann", - "Horn", - "Huber", - "Jung", - "Jäger", - "Kaiser", - "Keller", - "Klein", - "Koch", - "Kraus", - "Krause", - "Krämer", - "Krüger", - "Kuhn", - "Köhler", - "König", - "Kühn", - "Lang", - "Lange", - "Lehmann", - "Lorenz", - "Ludwig", - "Maier", - "Martin", - "Mayer", - "Meier", - "Meyer", - "Möller", - "Müller", - "Neumann", - "Otto", - "Peters", - "Pfeiffer", - "Pohl", - "Richter", - "Roth", - "Sauer", - "Schmid", - "Schmidt", - "Schmitt", - "Schmitz", - "Schneider", - "Scholz", - "Schreiber", - "Schröder", - "Schubert", - "Schulte", - "Schulz", - "Schulze", - "Schumacher", - "Schuster", - "Schwarz", - "Schäfer", - "Seidel", - "Simon", - "Sommer", - "Stein", - "Thomas", - "Vogel", - "Vogt", - "Voigt", - "Wagner", - "Walter", - "Weber", - "Weiß", - "Werner", - "Winkler", - "Winter", - "Wolf", - "Wolff", - "Ziegler", - "Zimmermann" - }, - finnish = { - --- - -- Data provided by the Finnish Population Register Centre (avoindata.fi) - -- License: CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/) - -- - -- Changes: - -- * Changed encoding from iso-8859-9 to UTF-8 - -- * Extracted a fraction of the original data and ported it from the - -- original .csv to the .lua format in this file - -- - "Aalto", - "Aaltonen", - "Aarnio", - "Aho", - "Ahokas", - "Ahola", - "Ahonen", - "Airaksinen", - "Alanen", - "Alanko", - "Alatalo", - "Alho", - "Andersson", - "Antikainen", - "Antila", - "Anttila", - "Anttonen", - "Aro", - "Arola", - "Asikainen", - "Autio", - "Auvinen", - "Backman", - "Berg", - "Bergman", - "Björklund", - "Blomqvist", - "Eerola", - "Eklund", - "Ekman", - "Elo", - "Eloranta", - "Eriksson", - "Erkkilä", - "Eronen", - "Eskelinen", - "Eskola", - "Forsman", - "Forsström", - "Friman", - "Grönroos", - "Gustafsson", - "Haapala", - "Haapanen", - "Haapaniemi", - "Haataja", - "Haavisto", - "Hakala", - "Hakanen", - "Hakkarainen", - "Halme", - "Halonen", - "Hannula", - "Harju", - "Hartikainen", - "Hassinen", - "Hautala", - "Hautamäki", - "Haverinen", - "Heikkilä", - "Heikkinen", - "Heino", - "Heinonen", - "Heiskanen", - "Helenius", - "Helin", - "Helminen", - "Henriksson", - "Hietala", - "Hietanen", - "Hiltunen", - "Hirvonen", - "Hokkanen", - "Holappa", - "Holm", - "Holmberg", - "Holmström", - "Holopainen", - "Honkanen", - "Huhtala", - "Huotari", - "Huovinen", - "Huttunen", - "Huusko", - "Huuskonen", - "Hynninen", - "Hyttinen", - "Hytönen", - "Hyvärinen", - "Hyvönen", - "Hyytiäinen", - "Häkkinen", - "Hämäläinen", - "Hänninen", - "Härkönen", - "Ihalainen", - "Ikonen", - "Immonen", - "Inkinen", - "Jaakkola", - "Jaatinen", - "Jalonen", - "Jansson", - "Jantunen", - "Jauhiainen", - "Joensuu", - "Johansson", - "Jokela", - "Jokinen", - "Junttila", - "Juntunen", - "Jussila", - "Juvonen", - "Jylhä", - "Jäntti", - "Järvelä", - "Järvenpää", - "Järvi", - "Järvinen", - "Jääskeläinen", - "Kaikkonen", - "Kainulainen", - "Kallio", - "Kalliokoski", - "Kanerva", - "Kangas", - "Kankaanpää", - "Kantola", - "Karhu", - "Karhunen", - "Kari", - "Karjalainen", - "Karlsson", - "Karppinen", - "Karttunen", - "Karvinen", - "Karvonen", - "Kauhanen", - "Kauppi", - "Kauppila", - "Kauppinen", - "Keinänen", - "Kekkonen", - "Kemppainen", - "Keränen", - "Keskinen", - "Keskitalo", - "Ketola", - "Kettunen", - "Kiiski", - "Kiiskinen", - "Kilpeläinen", - "Kinnunen", - "Kiuru", - "Kivelä", - "Kivimäki", - "Kivinen", - "Kiviniemi", - "Kivistö", - "Koistinen", - "Koivisto", - "Koivula", - "Koivunen", - "Koivuniemi", - "Kokko", - "Kokkonen", - "Kolehmainen", - "Koljonen", - "Komulainen", - "Kontio", - "Konttinen", - "Koponen", - "Korhonen", - "Korpela", - "Korpi", - "Kortelainen", - "Koskela", - "Koski", - "Koskinen", - "Kosonen", - "Kotilainen", - "Kovanen", - "Kuisma", - "Kujala", - "Kujanpää", - "Kukkonen", - "Kulmala", - "Kumpulainen", - "Kuokkanen", - "Kuosmanen", - "Kurki", - "Kuronen", - "Kuusela", - "Kuusisto", - "Kyllönen", - "Kähkönen", - "Kämäräinen", - "Kärki", - "Kärkkäinen", - "Kääriäinen", - "Laakkonen", - "Laakso", - "Laaksonen", - "Laamanen", - "Lahti", - "Lahtinen", - "Laiho", - "Laine", - "Laitila", - "Laitinen", - "Lammi", - "Lampinen", - "Lankinen", - "Lappalainen", - "Lassila", - "Latvala", - "Laukkanen", - "Laurila", - "Lehikoinen", - "Lehti", - "Lehtimäki", - "Lehtinen", - "Lehto", - "Lehtola", - "Lehtonen", - "Leino", - "Leinonen", - "Lempinen", - "Lepistö", - "Leppälä", - "Leppänen", - "Leskinen", - "Liimatainen", - "Lilja", - "Lind", - "Lindberg", - "Lindfors", - "Lindgren", - "Lindholm", - "Lindqvist", - "Lindroos", - "Lindström", - "Linna", - "Lipponen", - "Liukkonen", - "Luoma", - "Luostarinen", - "Luoto", - "Luukkonen", - "Lyytikäinen", - "Lähteenmäki", - "Lämsä", - "Makkonen", - "Malinen", - "Manninen", - "Markkanen", - "Martikainen", - "Marttila", - "Marttinen", - "Matikainen", - "Matilainen", - "Mattila", - "Mattsson", - "Meriläinen", - "Miettinen", - "Mikkola", - "Mikkonen", - "Moilanen", - "Moisio", - "Mononen", - "Muhonen", - "Mustonen", - "Myllymäki", - "Mäenpää", - "Mäkelä", - "Mäki", - "Mäkinen", - "Mäkitalo", - "Männistö", - "Mäntylä", - "Määttä", - "Nenonen", - "Neuvonen", - "Nevala", - "Nevalainen", - "Niemelä", - "Niemi", - "Nieminen", - "Niiranen", - "Nikula", - "Niskanen", - "Nissinen", - "Nordström", - "Nousiainen", - "Nurmi", - "Nurminen", - "Nuutinen", - "Nyberg", - "Nygård", - "Nyholm", - "Nykänen", - "Nylund", - "Nyman", - "Nyström", - "Närhi", - "Oikarinen", - "Oinonen", - "Ojala", - "Ojanen", - "Oksanen", - "Ollikainen", - "Ollila", - "Paajanen", - "Paananen", - "Paavilainen", - "Paavola", - "Pajunen", - "Pakarinen", - "Pakkanen", - "Palomäki", - "Parkkinen", - "Partanen", - "Parviainen", - "Pasanen", - "Pehkonen", - "Pekkala", - "Pekkarinen", - "Pelkonen", - "Peltola", - "Peltonen", - "Peltoniemi", - "Pennanen", - "Penttilä", - "Penttinen", - "Perälä", - "Pesonen", - "Pietikäinen", - "Pietilä", - "Piirainen", - "Piiroinen", - "Pikkarainen", - "Pirinen", - "Pitkänen", - "Pohjola", - "Puhakka", - "Pulkkinen", - "Puranen", - "Puustinen", - "Pääkkönen", - "Pöllänen", - "Pöyhönen", - "Raatikainen", - "Rajala", - "Rajamäki", - "Ranta", - "Rantala", - "Rantanen", - "Rauhala", - "Rautiainen", - "Rautio", - "Reinikainen", - "Repo", - "Riihimäki", - "Riikonen", - "Rinne", - "Rintala", - "Rissanen", - "Ronkainen", - "Rossi", - "Ruohonen", - "Ruotsalainen", - "Ruuska", - "Ruuskanen", - "Rytkönen", - "Ryynänen", - "Räisänen", - "Räsänen", - "Räty", - "Rönkkö", - "Saarela", - "Saarelainen", - "Saari", - "Saarinen", - "Saastamoinen", - "Sainio", - "Sallinen", - "Salmela", - "Salmi", - "Salminen", - "Salo", - "Salomaa", - "Salonen", - "Sandberg", - "Savolainen", - "Seppä", - "Seppälä", - "Seppänen", - "Sihvonen", - "Siitonen", - "Sillanpää", - "Silvennoinen", - "Simola", - "Simonen", - "Sinisalo", - "Sinkkonen", - "Sipilä", - "Sirviö", - "Sirén", - "Sivonen", - "Sjöblom", - "Snellman", - "Soini", - "Soininen", - "Sormunen", - "Sorsa", - "Suhonen", - "Sundström", - "Suomalainen", - "Suomela", - "Suomi", - "Suominen", - "Sutinen", - "Suutari", - "Suvanto", - "Syrjälä", - "Tahvanainen", - "Taipale", - "Takala", - "Tamminen", - "Tanskanen", - "Tapio", - "Tarvainen", - "Taskinen", - "Tenhunen", - "Tervo", - "Tervonen", - "Tiainen", - "Tiihonen", - "Tikka", - "Tikkanen", - "Timonen", - "Tirkkonen", - "Toivanen", - "Toivola", - "Toivonen", - "Tolonen", - "Tolvanen", - "Tuomainen", - "Tuomi", - "Tuominen", - "Tuomisto", - "Tuovinen", - "Turpeinen", - "Turunen", - "Tähtinen", - "Törmänen", - "Ukkonen", - "Uotila", - "Uusitalo", - "Vainio", - "Vainionpää", - "Valkama", - "Valkonen", - "Valtonen", - "Vanhanen", - "Varis", - "Vartiainen", - "Venäläinen", - "Vepsäläinen", - "Vesterinen", - "Viitala", - "Viitanen", - "Viljanen", - "Virolainen", - "Virta", - "Virtanen", - "Voutilainen", - "Vuorela", - "Vuori", - "Vuorinen", - "Vuorio", - "Väisänen", - "Välimäki", - "Väyrynen", - "Väänänen", - "Westerlund", - "Wikström", - "Ylinen", - "Ylitalo", - "Ylönen" - }, - russian = { - "Abramtsov", - "Akimov", - "Aksenov", - "Aleksandrov", - "Anisimov", - "Arkhipov", - "Baranov", - "Belousov", - "Belov", - "Belyaev", - "Belyakov", - "Biryukov", - "Bogdanov", - "Boricov", - "Bykov", - "Chernov", - "Chernyshev", - "Chistyakov", - "Danilov", - "Demidov", - "Denisov", - "Dmitriev", - "Efimov", - "Egorov", - "Ermakov", - "Evdokimov", - "Fedorov", - "Filatov", - "Filimonov", - "Filippov", - "Fomin", - "Frolov", - "Gerasimov", - "Golubev", - "Grigorev", - "Gromov", - "Ignatev", - "Ilin", - "Ivanov", - "Karpov", - "Khokhlov", - "Kiselev", - "Klimov", - "Kolesnikov", - "Kovalenko", - "Kovalev", - "Kozlov", - "Kuzmin", - "Kyznetsov", - "Larionov", - "Lebedev", - "Leonov", - "Lobanov", - "Loginov", - "Lukanov", - "Lukin", - "Melnikov", - "Mikhailov", - "Mironov", - "Morozov", - "Nazarov", - "Nesterov", - "Nikitin", - "Nikolaev", - "Novikov", - "Osipov", - "Pavlov", - "Petrenko", - "Petrov", - "Polyakov", - "Ponomarev", - "Popov", - "Prokhorov", - "Savelev", - "Savin", - "Semenov", - "Shcherbakov", - "Smirnov", - "Sokolov", - "Sorokin", - "Spiridonov", - "Stepanov", - "Suvorov", - "Tarasov", - "Tikhomirov", - "Tikhonov", - "Timofeev", - "Titov", - "Ushakov", - "Vasilev", - "Vlasov", - "Volkv", - "Vorobev", - "Voronin", - "Voronov", - "Yakovlev", - "Yudin", - "Zaitsev", - "Zakharov", - "Zhuravlev" - }, - british = { - "Abbott", - "Adams", - "Ahmed", - "Akhtar", - "Alexander", - "Ali", - "Allan", - "Allen", - "Anderson", - "Andrews", - "Archer", - "Armstrong", - "Arnold", - "Ashton", - "Atkins", - "Atkinson", - "Austin", - "Bailey", - "Baker", - "Baldwin", - "Ball", - "Banks", - "Barber", - "Barker", - "Barlow", - "Barnes", - "Barnett", - "Barrett", - "Barry", - "Bartlett", - "Barton", - "Bates", - "Baxter", - "Begum", - "Bell", - "Bennett", - "Benson", - "Bentley", - "Berry", - "Bevan", - "Bibi", - "Birch", - "Bird", - "Bishop", - "Black", - "Blackburn", - "Blake", - "Bolton", - "Bond", - "Booth", - "Bowen", - "Boyle", - "Bradley", - "Bradshaw", - "Brady", - "Bray", - "Brennan", - "Briggs", - "Brookes", - "Brooks", - "Brown", - "Browne", - "Bruce", - "Bryan", - "Bryant", - "Buckley", - "Bull", - "Burgess", - "Burke", - "Burns", - "Burrows", - "Burton", - "Butcher", - "Butler", - "Byrne", - "Cameron", - "Campbell", - "Carey", - "Carpenter", - "Carr", - "Carroll", - "Carter", - "Cartwright", - "Chadwick", - "Chamberlain", - "Chambers", - "Chan", - "Chandler", - "Chapman", - "Charlton", - "Clark", - "Clarke", - "Clayton", - "Clements", - "Coates", - "Cole", - "Coleman", - "Coles", - "Collier", - "Collins", - "Connolly", - "Connor", - "Conway", - "Cook", - "Cooke", - "Cooper", - "Cox", - "Craig", - "Crawford", - "Cross", - "Cunningham", - "Curtis", - "Dale", - "Daly", - "Daniels", - "Davey", - "Davidson", - "Davies", - "Davis", - "Davison", - "Dawson", - "Day", - "Dean", - "Dennis", - "Dickinson", - "Dixon", - "Dobson", - "Dodd", - "Doherty", - "Donnelly", - "Douglas", - "Doyle", - "Duffy", - "Duncan", - "Dunn", - "Dyer", - "Edwards", - "Elliott", - "Ellis", - "Evans", - "Farmer", - "Farrell", - "Faulkner", - "Ferguson", - "Field", - "Finch", - "Fisher", - "Fitzgerald", - "Fleming", - "Fletcher", - "Flynn", - "Ford", - "Forster", - "Foster", - "Fowler", - "Fox", - "Francis", - "Franklin", - "Fraser", - "Freeman", - "French", - "Frost", - "Fry", - "Fuller", - "Gallagher", - "Gardiner", - "Gardner", - "Garner", - "George", - "Gibbons", - "Gibbs", - "Gibson", - "Gilbert", - "Giles", - "Gill", - "Glover", - "Goddard", - "Godfrey", - "Goodwin", - "Gordon", - "Gough", - "Gould", - "Graham", - "Grant", - "Gray", - "Green", - "Greenwood", - "Gregory", - "Griffin", - "Griffiths", - "Hale", - "Hall", - "Hamilton", - "Hammond", - "Hancock", - "Hanson", - "Harding", - "Hardy", - "Hargreaves", - "Harper", - "Harris", - "Harrison", - "Hart", - "Hartley", - "Harvey", - "Hawkins", - "Hayes", - "Haynes", - "Hayward", - "Heath", - "Henderson", - "Henry", - "Herbert", - "Hewitt", - "Hicks", - "Higgins", - "Hill", - "Hilton", - "Hobbs", - "Hodgson", - "Holden", - "Holland", - "Holloway", - "Holmes", - "Holt", - "Hooper", - "Hope", - "Hopkins", - "Horton", - "Houghton", - "Howard", - "Howarth", - "Howe", - "Howell", - "Howells", - "Hudson", - "Hughes", - "Humphreys", - "Humphries", - "Hunt", - "Hunter", - "Hurst", - "Hussain", - "Hutchinson", - "Hyde", - "Ingram", - "Iqbal", - "Jackson", - "James", - "Jarvis", - "Jenkins", - "Jennings", - "John", - "Johnson", - "Johnston", - "Jones", - "Jordan", - "Joyce", - "Kaur", - "Kay", - "Kelly", - "Kemp", - "Kennedy", - "Kent", - "Kerr", - "Khan", - "King", - "Kirby", - "Kirk", - "Knight", - "Knowles", - "Lamb", - "Lambert", - "Lane", - "Law", - "Lawrence", - "Lawson", - "Leach", - "Lee", - "Lees", - "Leonard", - "Lewis", - "Little", - "Lloyd", - "Long", - "Lord", - "Lowe", - "Lucas", - "Lynch", - "Lyons", - "Macdonald", - "Mahmood", - "Mann", - "Manning", - "Marsden", - "Marsh", - "Marshall", - "Martin", - "Mason", - "Matthews", - "May", - "Mccarthy", - "Mcdonald", - "Mckenzie", - "Mclean", - "Mellor", - "Metcalfe", - "Miah", - "Middleton", - "Miles", - "Miller", - "Mills", - "Mistry", - "Mitchell", - "Moore", - "Moran", - "Morgan", - "Morley", - "Morris", - "Morrison", - "Morton", - "Moss", - "Murphy", - "Murray", - "Myers", - "Nash", - "Naylor", - "Nelson", - "Newman", - "Newton", - "Nicholls", - "Nicholson", - "Nixon", - "Noble", - "Nolan", - "Norman", - "Norris", - "North", - "Norton", - "O'brien", - "O'connor", - "O'donnell", - "O'neill", - "O'sullivan", - "Oliver", - "Osborne", - "Owen", - "Owens", - "Page", - "Palmer", - "Parker", - "Parkes", - "Parkin", - "Parkinson", - "Parry", - "Parsons", - "Patel", - "Patterson", - "Payne", - "Peacock", - "Pearce", - "Pearson", - "Perkins", - "Perry", - "Peters", - "Phillips", - "Pickering", - "Pollard", - "Poole", - "Pope", - "Porter", - "Potter", - "Potts", - "Powell", - "Power", - "Pratt", - "Preston", - "Price", - "Pritchard", - "Pugh", - "Quinn", - "Rahman", - "Randall", - "Read", - "Reed", - "Rees", - "Reeves", - "Reid", - "Reynolds", - "Rhodes", - "Rice", - "Richards", - "Richardson", - "Riley", - "Roberts", - "Robertson", - "Robinson", - "Robson", - "Rogers", - "Rose", - "Ross", - "Rowe", - "Rowley", - "Russell", - "Ryan", - "Sanders", - "Sanderson", - "Saunders", - "Savage", - "Schofield", - "Scott", - "Shah", - "Sharp", - "Sharpe", - "Shaw", - "Shepherd", - "Sheppard", - "Short", - "Simmons", - "Simpson", - "Sims", - "Sinclair", - "Singh", - "Skinner", - "Slater", - "Smart", - "Smith", - "Spencer", - "Stanley", - "Steele", - "Stephens", - "Stephenson", - "Stevens", - "Stevenson", - "Stewart", - "Stokes", - "Stone", - "Storey", - "Sullivan", - "Summers", - "Sutton", - "Swift", - "Sykes", - "Talbot", - "Taylor", - "Thomas", - "Thompson", - "Thomson", - "Thornton", - "Thorpe", - "Todd", - "Tomlinson", - "Townsend", - "Tucker", - "Turnbull", - "Turner", - "Tyler", - "Vaughan", - "Vincent", - "Wade", - "Walker", - "Wall", - "Wallace", - "Wallis", - "Walsh", - "Walters", - "Walton", - "Ward", - "Warner", - "Warren", - "Waters", - "Watkins", - "Watson", - "Watts", - "Webb", - "Webster", - "Welch", - "Wells", - "West", - "Weston", - "Wheeler", - "White", - "Whitehead", - "Whitehouse", - "Whittaker", - "Wilkins", - "Wilkinson", - "Williams", - "Williamson", - "Willis", - "Wilson", - "Winter", - "Wong", - "Wood", - "Woods", - "Woodward", - "Wright", - "Wyatt", - "Yates", - "Young" - } -} diff --git a/res/data/maps/2/Map_Ground.png b/res/data/maps/2/Map_Ground.png deleted file mode 100644 index e4a6a30e..00000000 Binary files a/res/data/maps/2/Map_Ground.png and /dev/null differ diff --git a/res/data/maps/2/Map_Objects.png b/res/data/maps/2/Map_Objects.png deleted file mode 100644 index ebb80528..00000000 Binary files a/res/data/maps/2/Map_Objects.png and /dev/null differ diff --git a/res/data/maps/2/Map_Spawns.png b/res/data/maps/2/Map_Spawns.png deleted file mode 100644 index e24c6849..00000000 Binary files a/res/data/maps/2/Map_Spawns.png and /dev/null differ diff --git a/res/data/maps/2/info.lua b/res/data/maps/2/info.lua deleted file mode 100644 index 9d05de4a..00000000 --- a/res/data/maps/2/info.lua +++ /dev/null @@ -1,22 +0,0 @@ -return { - name = 'test2', - ground = { - { r = 75, g = 105, b = 47, tile = 'tile_grass' }, - { r = 105, g = 106, b = 106, tile = 'tile_asphalt' }, - { r = 89, g = 86, b = 82, tile = 'tile_gravel' }, - { r = 102, g = 57, b = 49, tile = 'tile_soil' }, - { r = 143, g = 86, b = 59, tile = 'tile_woodenfloor' } - }, - objects = { - { r = 0, g = 0, b = 0, object = 'worldobject_wall' }, - { r = 5, g = 47, b = 21, object = 'worldobject_tree' }, - { r = 251, g = 242, b = 54, object = 'worldobject_door' }, - { r = 99, g = 155, b = 255, object = 'worldobject_window' }, - { r = 132, g = 126, b = 135, object = 'worldobject_lowwall' }, - { r = 223, g = 113, b = 38, object = 'worldobject_crate' } - }, - spawns = { - { r = 48, g = 96, b = 130, type = 'allied' }, - { r = 172, g = 50, b = 50, type = 'enemy' } - } -} diff --git a/res/data/procgen/layouts/small.lua b/res/data/procgen/layouts/small.lua new file mode 100644 index 00000000..da0f3555 --- /dev/null +++ b/res/data/procgen/layouts/small.lua @@ -0,0 +1,277 @@ +return { + dimensions = { + height = 6, + width = 8 + } --[[table: 0x09be67b8]], + parcels = { + foliage = { + { + h = 1, + w = 1, + x = 0, + y = 0 + } --[[table: 0x09bec320]], + { + h = 1, + w = 1, + x = 0, + y = 1 + } --[[table: 0x09bec3b0]], + { + h = 1, + w = 1, + x = 0, + y = 2 + } --[[table: 0x09bec440]], + { + h = 1, + w = 1, + x = 0, + y = 3 + } --[[table: 0x09bec4d0]], + { + h = 1, + w = 1, + x = 0, + y = 4 + } --[[table: 0x09bec560]], + { + h = 1, + w = 1, + x = 0, + y = 5 + } --[[table: 0x09bec5f0]], + { + h = 1, + w = 1, + x = 2, + y = 0 + } --[[table: 0x09bec680]], + { + h = 1, + w = 1, + x = 2, + y = 5 + } --[[table: 0x09bec710]], + { + h = 1, + w = 1, + x = 3, + y = 0 + } --[[table: 0x09bec7a0]], + { + h = 1, + w = 1, + x = 3, + y = 5 + } --[[table: 0x09bec8c0]], + { + h = 1, + w = 1, + x = 4, + y = 0 + } --[[table: 0x09bec950]], + { + h = 1, + w = 1, + x = 4, + y = 5 + } --[[table: 0x09bec9e0]], + { + h = 1, + w = 1, + x = 5, + y = 0 + } --[[table: 0x09beca70]], + { + h = 1, + w = 1, + x = 5, + y = 5 + } --[[table: 0x09becb00]], + { + h = 1, + w = 1, + x = 6, + y = 0 + } --[[table: 0x09becb90]], + { + h = 1, + w = 1, + x = 6, + y = 5 + } --[[table: 0x09becc20]], + { + h = 1, + w = 1, + x = 7, + y = 0 + } --[[table: 0x09beccb0]], + { + h = 1, + w = 1, + x = 7, + y = 1 + } --[[table: 0x09bece50]], + { + h = 1, + w = 1, + x = 7, + y = 2 + } --[[table: 0x09becee0]], + { + h = 1, + w = 1, + x = 7, + y = 3 + } --[[table: 0x09becf70]], + { + h = 1, + w = 1, + x = 7, + y = 4 + } --[[table: 0x09bed000]], + { + h = 1, + w = 1, + x = 7, + y = 5 + } --[[table: 0x09bed090]] + } --[[table: 0x09be6920]], + huge = { + { + h = 2, + w = 4, + x = 2, + y = 2 + } --[[table: 0x09bec880]] + } --[[table: 0x09be69c0]], + large = {} --[[table: 0x09be6998]], + medium = {} --[[table: 0x09be6970]], + road = { + { + h = 1, + w = 1, + x = 1, + y = 0 + } --[[table: 0x0d5aa638]], + { + h = 1, + w = 1, + x = 1, + y = 1 + } --[[table: 0x0d5aa748]], + { + h = 1, + w = 1, + x = 1, + y = 2 + } --[[table: 0x0d5ab9e8]], + { + h = 1, + w = 1, + x = 1, + y = 3 + } --[[table: 0x0d5ab960]], + { + h = 1, + w = 1, + x = 1, + y = 4 + } --[[table: 0x0d5aa970]], + { + h = 1, + w = 1, + x = 1, + y = 5 + } --[[table: 0x0d5aaa50]], + { + h = 1, + w = 1, + x = 2, + y = 1 + } --[[table: 0x0d5aaae0]], + { + h = 1, + w = 1, + x = 2, + y = 4 + } --[[table: 0x0d5aab70]], + { + h = 1, + w = 1, + x = 3, + y = 1 + } --[[table: 0x0d5aac00]], + { + h = 1, + w = 1, + x = 3, + y = 4 + } --[[table: 0x0d5ab230]], + { + h = 1, + w = 1, + x = 4, + y = 1 + } --[[table: 0x0d5ab2c0]], + { + h = 1, + w = 1, + x = 4, + y = 4 + } --[[table: 0x0d5ab448]], + { + h = 1, + w = 1, + x = 5, + y = 1 + } --[[table: 0x0d5ab4d8]], + { + h = 1, + w = 1, + x = 5, + y = 4 + } --[[table: 0x0d5ab568]], + { + h = 1, + w = 1, + x = 6, + y = 1 + } --[[table: 0x0d5ab5f8]], + { + h = 1, + w = 1, + x = 6, + y = 2 + } --[[table: 0x0d5ab688]], + { + h = 1, + w = 1, + x = 6, + y = 3 + } --[[table: 0x0d5ab878]], + { + h = 1, + w = 1, + x = 6, + y = 4 + } --[[table: 0x0d5ab908]], + { + h = 1, + w = 1, + x = 7, + y = 1 + } --[[table: 0x0d5ab208]] + } --[[table: 0x09be68f8]], + small = {} --[[table: 0x09be6948]], + spawns = { + { + h = 1, + w = 1, + x = 1, + y = 1 + } --[[table: 0x09be6e50]] + } --[[table: 0x09be68d0]] + } --[[table: 0x09be67e0]] +} --[[table: 0x09be6758]] \ No newline at end of file diff --git a/res/data/procgen/layouts/small2.lua b/res/data/procgen/layouts/small2.lua new file mode 100644 index 00000000..fa100e64 --- /dev/null +++ b/res/data/procgen/layouts/small2.lua @@ -0,0 +1,298 @@ +return { + dimensions = { + height = 8, + width = 8 + } --[[table: 0x09bf01f8]], + parcels = { + foliage = { + { + h = 1, + w = 1, + x = 0, + y = 0 + } --[[table: 0x0d79b478]], + { + h = 1, + w = 1, + x = 0, + y = 1 + } --[[table: 0x0d79b508]], + { + h = 1, + w = 1, + x = 0, + y = 2 + } --[[table: 0x0d79b598]], + { + h = 1, + w = 1, + x = 0, + y = 3 + } --[[table: 0x0d79b658]], + { + h = 1, + w = 1, + x = 0, + y = 4 + } --[[table: 0x0d5aa240]], + { + h = 1, + w = 1, + x = 0, + y = 5 + } --[[table: 0x0d79b628]], + { + h = 1, + w = 1, + x = 0, + y = 6 + } --[[table: 0x0d79cb20]], + { + h = 1, + w = 1, + x = 0, + y = 7 + } --[[table: 0x0d79cbb0]], + { + h = 1, + w = 1, + x = 1, + y = 0 + } --[[table: 0x0d79cc40]], + { + h = 1, + w = 1, + x = 1, + y = 1 + } --[[table: 0x0d79cd60]], + { + h = 1, + w = 1, + x = 1, + y = 6 + } --[[table: 0x0d79cdf0]], + { + h = 1, + w = 1, + x = 1, + y = 7 + } --[[table: 0x0d79ce80]], + { + h = 1, + w = 1, + x = 2, + y = 0 + } --[[table: 0x0d79cf10]], + { + h = 1, + w = 1, + x = 2, + y = 7 + } --[[table: 0x0d79cfa0]], + { + h = 1, + w = 1, + x = 4, + y = 7 + } --[[table: 0x0d5aa7d8]], + { + h = 1, + w = 1, + x = 5, + y = 0 + } --[[table: 0x0d79d310]], + { + h = 1, + w = 1, + x = 5, + y = 7 + } --[[table: 0x0d79d3a0]], + { + h = 1, + w = 1, + x = 6, + y = 0 + } --[[table: 0x0d79d540]], + { + h = 1, + w = 1, + x = 6, + y = 1 + } --[[table: 0x0d79d5d0]], + { + h = 1, + w = 1, + x = 6, + y = 6 + } --[[table: 0x0d79d660]], + { + h = 1, + w = 1, + x = 6, + y = 7 + } --[[table: 0x0d79d6f0]], + { + h = 1, + w = 1, + x = 7, + y = 0 + } --[[table: 0x0d79d780]], + { + h = 1, + w = 1, + x = 7, + y = 1 + } --[[table: 0x0d79d810]], + { + h = 1, + w = 1, + x = 7, + y = 2 + } --[[table: 0x0d79d8a0]], + { + h = 1, + w = 1, + x = 7, + y = 4 + } --[[table: 0x0d79d930]], + { + h = 1, + w = 1, + x = 7, + y = 5 + } --[[table: 0x0d79d9c0]], + { + h = 1, + w = 1, + x = 7, + y = 6 + } --[[table: 0x0d79da50]], + { + h = 1, + w = 1, + x = 7, + y = 7 + } --[[table: 0x0d5aa268]] + } --[[table: 0x09bf0298]], + huge = { + { + h = 4, + w = 2, + x = 4, + y = 4 + } --[[table: 0x0d7a41d0]] + } --[[table: 0x09bf0338]], + large = { + { + h = 2, + w = 2, + x = 4, + y = 1 + } --[[table: 0x0d7a1bf0]] + } --[[table: 0x09bf0310]], + medium = { + { + h = 1, + w = 2, + x = 1, + y = 4 + } --[[table: 0x0d79f6f8]] + } --[[table: 0x09bf02e8]], + road = { + { + h = 1, + w = 1, + x = 3, + y = 0 + } --[[table: 0x0d79acb0]], + { + h = 1, + w = 1, + x = 3, + y = 1 + } --[[table: 0x0d79ad60]], + { + h = 1, + w = 1, + x = 3, + y = 2 + } --[[table: 0x0d79adf0]], + { + h = 1, + w = 1, + x = 3, + y = 3 + } --[[table: 0x0d79aeb0]], + { + h = 1, + w = 1, + x = 3, + y = 4 + } --[[table: 0x0d79af40]], + { + h = 1, + w = 1, + x = 3, + y = 5 + } --[[table: 0x0d79ae80]], + { + h = 1, + w = 1, + x = 3, + y = 6 + } --[[table: 0x0d79b088]], + { + h = 1, + w = 1, + x = 3, + y = 7 + } --[[table: 0x0d79b118]], + { + h = 1, + w = 1, + x = 4, + y = 3 + } --[[table: 0x0d79b1a8]], + { + h = 1, + w = 1, + x = 5, + y = 3 + } --[[table: 0x0d79b2c8]], + { + h = 1, + w = 1, + x = 6, + y = 3 + } --[[table: 0x0d79b358]], + { + h = 1, + w = 1, + x = 7, + y = 3 + } --[[table: 0x0d79b3e8]] + } --[[table: 0x09bf0270]], + small = { + { + h = 1, + w = 1, + x = 2, + y = 2 + } --[[table: 0x0d79cd38]] + } --[[table: 0x09bf02c0]], + spawns = { + { + h = 1, + w = 1, + x = 3, + y = 7 + } --[[table: 0x0d79a800]], + { + h = 1, + w = 1, + x = 7, + y = 3 + } --[[table: 0x0d79a8b0]] + } --[[table: 0x09bf0248]] + } --[[table: 0x09bf0220]] +} --[[table: 0x09bf0198]] \ No newline at end of file diff --git a/res/data/procgen/prefabs/house/ground.png b/res/data/procgen/prefabs/house/ground.png new file mode 100644 index 00000000..5ed7ae0b Binary files /dev/null and b/res/data/procgen/prefabs/house/ground.png differ diff --git a/res/data/procgen/prefabs/house/info.lua b/res/data/procgen/prefabs/house/info.lua new file mode 100644 index 00000000..f24355a9 --- /dev/null +++ b/res/data/procgen/prefabs/house/info.lua @@ -0,0 +1,20 @@ +return { + name = 'house', + type = 'huge', + ground = { + { r = 0, g = 0, b = 0, id = 'tile_asphalt' }, + { r = 102, g = 57, b = 49, id = 'tile_woodenfloor' }, + { r = 106, g = 190, b = 48, id = 'tile_grass' }, + { r = 48, g = 96, b = 130, id = 'tile_deep_water' } + }, + objects = { + { r = 0, g = 0, b = 0, id = 'worldobject_wall' }, + { r = 95, g = 205, b = 228, id = 'worldobject_window' }, + { r = 223, g = 113, b = 38, id = 'worldobject_door' }, + { r = 105, g = 106, b = 106, id = 'worldobject_chair' }, + { r = 89, g = 86, b = 82, id = 'worldobject_table' }, + { r = 251, g = 242, b = 54, id = 'worldobject_crate' }, + { r = 238, g = 195, b = 154, id = 'worldobject_fence' }, + { r = 75, g = 105, b = 47, id = 'worldobject_tree' }, + } +} diff --git a/res/data/procgen/prefabs/house/objects.png b/res/data/procgen/prefabs/house/objects.png new file mode 100644 index 00000000..ff842015 Binary files /dev/null and b/res/data/procgen/prefabs/house/objects.png differ diff --git a/res/data/procgen/prefabs/road/ground.png b/res/data/procgen/prefabs/road/ground.png new file mode 100644 index 00000000..97713861 Binary files /dev/null and b/res/data/procgen/prefabs/road/ground.png differ diff --git a/res/data/procgen/prefabs/road/info.lua b/res/data/procgen/prefabs/road/info.lua new file mode 100644 index 00000000..07ff486f --- /dev/null +++ b/res/data/procgen/prefabs/road/info.lua @@ -0,0 +1,8 @@ +return { + name = 'road', + type = 'road', + ground = { + { r = 105, g = 106, b = 106, id = 'tile_asphalt' } + }, + objects = {} +} diff --git a/res/data/procgen/prefabs/road/objects.png b/res/data/procgen/prefabs/road/objects.png new file mode 100644 index 00000000..ed539772 Binary files /dev/null and b/res/data/procgen/prefabs/road/objects.png differ diff --git a/res/data/procgen/prefabs/test/ground.png b/res/data/procgen/prefabs/test/ground.png new file mode 100644 index 00000000..8aa01017 Binary files /dev/null and b/res/data/procgen/prefabs/test/ground.png differ diff --git a/res/data/procgen/prefabs/test/info.lua b/res/data/procgen/prefabs/test/info.lua new file mode 100644 index 00000000..3dd512c2 --- /dev/null +++ b/res/data/procgen/prefabs/test/info.lua @@ -0,0 +1,16 @@ +return { + name = 'test', + type = 'small', + ground = { + { r = 0, g = 0, b = 0, id = 'tile_asphalt' }, + { r = 102, g = 57, b = 49, id = 'tile_woodenfloor' } + }, + objects = { + { r = 143, g = 86, b = 59, id = 'worldobject_wall' }, + { r = 99, g = 155, b = 255, id = 'worldobject_window' }, + { r = 223, g = 113, b = 38, id = 'worldobject_door' }, + { r = 105, g = 106, b = 106, id = 'worldobject_chair' }, + { r = 89, g = 86, b = 82, id = 'worldobject_table' }, + { r = 238, g = 195, b = 154, id = 'worldobject_crate' } + } +} diff --git a/res/data/procgen/prefabs/test/objects.png b/res/data/procgen/prefabs/test/objects.png new file mode 100644 index 00000000..db4fdac6 Binary files /dev/null and b/res/data/procgen/prefabs/test/objects.png differ diff --git a/res/data/procgen/prefabs/test_big/ground.png b/res/data/procgen/prefabs/test_big/ground.png new file mode 100644 index 00000000..f9138337 Binary files /dev/null and b/res/data/procgen/prefabs/test_big/ground.png differ diff --git a/res/data/procgen/prefabs/test_big/info.lua b/res/data/procgen/prefabs/test_big/info.lua new file mode 100644 index 00000000..d3118311 --- /dev/null +++ b/res/data/procgen/prefabs/test_big/info.lua @@ -0,0 +1,16 @@ +return { + name = 'test_big', + type = 'large', + ground = { + { r = 0, g = 0, b = 0, id = 'tile_asphalt' }, + { r = 102, g = 57, b = 49, id = 'tile_woodenfloor' } + }, + objects = { + { r = 143, g = 86, b = 59, id = 'worldobject_wall' }, + { r = 99, g = 155, b = 255, id = 'worldobject_window' }, + { r = 223, g = 113, b = 38, id = 'worldobject_door' }, + { r = 105, g = 106, b = 106, id = 'worldobject_chair' }, + { r = 89, g = 86, b = 82, id = 'worldobject_table' }, + { r = 238, g = 195, b = 154, id = 'worldobject_crate' } + } +} diff --git a/res/data/procgen/prefabs/test_big/objects.png b/res/data/procgen/prefabs/test_big/objects.png new file mode 100644 index 00000000..feca32a1 Binary files /dev/null and b/res/data/procgen/prefabs/test_big/objects.png differ diff --git a/res/data/procgen/prefabs/test_long/ground.png b/res/data/procgen/prefabs/test_long/ground.png new file mode 100644 index 00000000..dc9e938b Binary files /dev/null and b/res/data/procgen/prefabs/test_long/ground.png differ diff --git a/res/data/procgen/prefabs/test_long/info.lua b/res/data/procgen/prefabs/test_long/info.lua new file mode 100644 index 00000000..4d833019 --- /dev/null +++ b/res/data/procgen/prefabs/test_long/info.lua @@ -0,0 +1,16 @@ +return { + name = 'test_long', + type = 'medium', + ground = { + { r = 0, g = 0, b = 0, id = 'tile_asphalt' }, + { r = 75, g = 105, b = 47, id = 'tile_grass' } + }, + objects = { + { r = 143, g = 86, b = 59, id = 'worldobject_wall' }, + { r = 95, g = 205, b = 228, id = 'worldobject_window' }, + { r = 223, g = 113, b = 38, id = 'worldobject_door' }, + { r = 105, g = 106, b = 106, id = 'worldobject_chair' }, + { r = 89, g = 86, b = 82, id = 'worldobject_table' }, + { r = 238, g = 195, b = 154, id = 'worldobject_fence' } + } +} diff --git a/res/data/procgen/prefabs/test_long/objects.png b/res/data/procgen/prefabs/test_long/objects.png new file mode 100644 index 00000000..74e3c412 Binary files /dev/null and b/res/data/procgen/prefabs/test_long/objects.png differ diff --git a/res/text/de_DE/inventory_text.lua b/res/text/de_DE/inventory_text.lua index 60b00c16..c0a09c25 100644 --- a/res/text/de_DE/inventory_text.lua +++ b/res/text/de_DE/inventory_text.lua @@ -2,7 +2,7 @@ local locale = {}; locale.identifier = 'de_DE'; locale.strings = { - ['inventory_inventory'] = "Inventar", + ['inventory_character'] = "Inventar", ['inventory_base'] = "Basis Inventar", ['inventory_equipment'] = "Ausrüstung", ['inventory_tile_inventory'] = "Feld Inventar", diff --git a/res/text/de_DE/ui_text.lua b/res/text/de_DE/ui_text.lua index e2dac745..59a52aa6 100644 --- a/res/text/de_DE/ui_text.lua +++ b/res/text/de_DE/ui_text.lua @@ -5,6 +5,7 @@ locale.strings = { ['ui_tile'] = "Feld: ", ['ui_tile_unexplored'] = "Unerforscht", ['ui_weapon'] = "Waffe: ", + ['ui_ammo'] = "Munition: ", ['ui_mode'] = "Modus: ", ['ui_win'] = "Alle Gegner sind tot. Du hast gewonnen!\n\nDrücke eine Taste um fortzufahren...", ['ui_lose'] = "Dein Team ist tot. Du hast verloren!\n\nDrücke eine Taste um fortzufahren...", @@ -52,6 +53,9 @@ locale.strings = { ['ui_ingame_open_help'] = "Hilfe", ['ui_ingame_abort_mission'] = "Mission abbrechen", ['ui_ingame_exit'] = "Hauptmenü", + + -- Help Screen + ['ui_help_header'] = 'Hilfe' } return locale; diff --git a/res/text/en_EN/inventory_text.lua b/res/text/en_EN/inventory_text.lua index fb65f19c..da4117e5 100644 --- a/res/text/en_EN/inventory_text.lua +++ b/res/text/en_EN/inventory_text.lua @@ -2,7 +2,7 @@ local locale = {}; locale.identifier = 'en_EN'; locale.strings = { - ['inventory_inventory'] = "Inventory", + ['inventory_character'] = "Inventory", ['inventory_base'] = "Base Inventory", ['inventory_equipment'] = "Equipment", ['inventory_tile_inventory'] = "Tile Inventory", diff --git a/res/text/en_EN/ui_text.lua b/res/text/en_EN/ui_text.lua index 5857a205..0e47e025 100644 --- a/res/text/en_EN/ui_text.lua +++ b/res/text/en_EN/ui_text.lua @@ -5,6 +5,7 @@ locale.strings = { ['ui_tile'] = "Tile: ", ['ui_tile_unexplored'] = "Unexplored", ['ui_weapon'] = "Weapon: ", + ['ui_ammo'] = "Ammo: ", ['ui_mode'] = "Mode: ", ['ui_win'] = "All enemies are dead. You won!\n\nPress any key to continue...", ['ui_lose'] = "Your team is dead. You lose!\n\nPress any key to continue...", @@ -51,6 +52,9 @@ locale.strings = { ['ui_ingame_open_help'] = "Show help", ['ui_ingame_abort_mission'] = "Abort Mission", ['ui_ingame_exit'] = "Main menu", + + -- Help Screen + ['ui_help_header'] = 'Help' } return locale; diff --git a/res/texturepacks/default/colors.lua b/res/texturepacks/default/colors.lua index e7631124..9af8674a 100644 --- a/res/texturepacks/default/colors.lua +++ b/res/texturepacks/default/colors.lua @@ -93,9 +93,14 @@ return { ['ui_title_2'] = COLORS.DB17, ['ui_title_3'] = COLORS.DB17, + ['ui_label'] = COLORS.DB07, + ['ui_button'] = COLORS.DB16, ['ui_button_hot'] = COLORS.DB18, + ['ui_scrollbar_element'] = COLORS.DB01, + ['ui_scrollbar_cursor'] = COLORS.DB25, + ['ui_select_field'] = COLORS.DB16, ['ui_select_field_hot'] = COLORS.DB18, @@ -103,14 +108,25 @@ return { ['ui_text_dim'] = COLORS.DB01, ['ui_text'] = COLORS.DB20, - ['ui_equipment_highlight'] = COLORS.DB10, + ['ui_help_section'] = COLORS.DB24, + + ['ui_equipment_highlight'] = COLORS.DB12, ['ui_equipment_mouseover'] = COLORS.DB15, ['ui_equipment_item'] = COLORS.DB20, ['ui_equipment_empty'] = COLORS.DB23, + ['ui_inventory_headers'] = COLORS.DB20, ['ui_inventory_item'] = COLORS.DB20, ['ui_inventory_full'] = COLORS.DB27, + ['ui_inventory_stats_name'] = COLORS.DB12, + ['ui_inventory_stats_type'] = COLORS.DB24, + ['ui_inventory_stats_value'] = COLORS.DB20, + ['ui_inventory_description'] = COLORS.DB20, + + ['ui_inventory_drag_bg'] = COLORS.DB01, + ['ui_inventory_drag_text'] = COLORS.DB20, + ['ui_ap_cost'] = COLORS.DB27, ['ui_ap_cost_result'] = COLORS.DB10, diff --git a/res/texturepacks/default/sprites.lua b/res/texturepacks/default/sprites.lua index 62d9ab83..901e607c 100644 --- a/res/texturepacks/default/sprites.lua +++ b/res/texturepacks/default/sprites.lua @@ -61,8 +61,11 @@ return { ['ui_mouse_pointer_attack'] = 11, ['ui_mouse_pointer_interact'] = 30, + -- Scrollareas ['ui_scroll_area_up'] = 31, ['ui_scroll_area_down'] = 32, + ['ui_scrollbar_cursor'] = 180, + ['ui_scrollbar_element'] = 180, -- ============================== -- Creatures diff --git a/src/CombatState.lua b/src/CombatState.lua index 23602f08..adf4a2b1 100644 --- a/src/CombatState.lua +++ b/src/CombatState.lua @@ -7,7 +7,8 @@ -- ------------------------------------------------ local Object = require( 'src.Object' ); -local MapLoader = require( 'src.map.MapLoader' ) +local MapGenerator = require( 'src.map.procedural.MapGenerator' ) +local ProceduralMap = require( 'src.map.procedural.ProceduralMap' ) local Factions = require( 'src.characters.Factions' ); local ProjectileManager = require( 'src.items.weapons.ProjectileManager' ); local ExplosionManager = require( 'src.items.weapons.ExplosionManager' ); @@ -52,8 +53,15 @@ function CombatState.new() -- ------------------------------------------------ function self:init( playerFaction, savegame ) - map = MapLoader.createRandom() - map:init( savegame ); + local generator = MapGenerator.new() + generator:init() + + local tiles = generator:getTiles() + local mw, mh = generator:getTileGridDimensions() + + map = ProceduralMap.new( tiles, mw, mh ) + map:init() + map:setSpawnpoints( generator:getSpawnpoints() ) factions = Factions.new( map ); factions:addFaction( Faction.new( FACTIONS.ENEMY, true )) diff --git a/src/characters/CharacterFactory.lua b/src/characters/CharacterFactory.lua index 2d2132da..a8678c50 100644 --- a/src/characters/CharacterFactory.lua +++ b/src/characters/CharacterFactory.lua @@ -16,10 +16,10 @@ local CharacterFactory = {}; local WEAPON_TYPES = require( 'src.constants.WEAPON_TYPES' ) local NAME_FILE = 'res.data.Names' local NATIONALITY = { - 'german', - 'russian', - 'british', - 'finnish' + { id = 'german', weight = 10 }, + { id = 'russian', weight = 3 }, + { id = 'british', weight = 3 }, + { id = 'finnish', weight = 1 } } -- ------------------------------------------------ @@ -27,6 +27,7 @@ local NATIONALITY = { -- ------------------------------------------------ local names +local nationalityWeight -- ------------------------------------------------ -- Private Functions @@ -40,6 +41,35 @@ local function loadNames( path ) return require( path ) end +--- +-- Calculates the total weight of all nationalities used for their random +-- selection. +-- @treturn number The total weight. +-- +local function calculateNationalitiesWeight() + local weight = 0 + for i = 1, #NATIONALITY do + weight = weight + NATIONALITY[i].weight + end + return weight +end + +--- +-- Randomly chooses a nationality from the weighted list of nationalities. +-- @treturn string The selected nationality's id. +-- +local function chooseNationality() + local rnd = love.math.random( nationalityWeight ) + local weight = 0 + for i = 1, #NATIONALITY do + weight = weight + NATIONALITY[i].weight + if rnd <= weight then + return NATIONALITY[i].id + end + end + error( 'Random selection of nationality failed. No nationality found.' ) +end + --- -- Loads the character's weapon and adds ammunition to his inventory. -- @param weapon (Weapon) The weapon to load. @@ -90,6 +120,8 @@ end function CharacterFactory.init() Log.debug( "Load Creature-Names:" ) names = loadNames( NAME_FILE ) + + nationalityWeight = calculateNationalitiesWeight() end function CharacterFactory.loadCharacter( savedCharacter ) @@ -115,7 +147,7 @@ function CharacterFactory.newCharacter( type ) local character = Character.new() if type == 'human' then - local nationality = NATIONALITY[love.math.random( #NATIONALITY )] + local nationality = chooseNationality() character:setNationality( nationality ) character:setName( names[nationality][love.math.random( #names[nationality] )]) end diff --git a/src/constants/PARCEL_SIZE.lua b/src/constants/PARCEL_SIZE.lua new file mode 100644 index 00000000..d39de0f0 --- /dev/null +++ b/src/constants/PARCEL_SIZE.lua @@ -0,0 +1,14 @@ +local PARCEL_SIZE = {} + +PARCEL_SIZE.WIDTH = 8 +PARCEL_SIZE.HEIGHT = 8 + +-- Make table read-only. +return setmetatable( PARCEL_SIZE, { + __index = function( _, key ) + error( "Can't access constant value at key: " .. key ) + end, + __newindex = function() + error( "Can't change a constant value." ) + end +}) diff --git a/src/inventory/Inventory.lua b/src/inventory/Inventory.lua index c3e442b0..756fd4d2 100644 --- a/src/inventory/Inventory.lua +++ b/src/inventory/Inventory.lua @@ -336,6 +336,30 @@ function Inventory.new( weightLimit, volumeLimit ) end end + --- + -- Counts items of a certain type and id. + -- Items that are contained within a stack will be counted too. + -- + -- @tparam string type The item type to search for. + -- @tparam string id The item id to search for. + -- @treturn number The total amount of found items. + -- + function self:countItems( type, id ) + local count = 0 + for _, item in ipairs( items ) do + -- Match items based on type and id. + if item:getItemType() == type and item:getID() == id then + -- Count items in stacks. + if item:instanceOf( 'ItemStack' ) then + count = count + item:getItemCount() + else + count = count + 1 + end + end + end + return count + end + function self:receive( event, ... ) if event == 'CHANGE_VOLUME' then updateVolume( ... ); diff --git a/src/map/MapLoader.lua b/src/map/MapLoader.lua index 2af7e775..a1b3684c 100644 --- a/src/map/MapLoader.lua +++ b/src/map/MapLoader.lua @@ -59,6 +59,11 @@ end -- @treturn table The loaded mapTemplate (only if successful). -- local function load( src ) + if not love.filesystem.exists( src .. INFO_FILE .. '.lua' ) then + Log.warn( string.format( 'Can\'t find info file. Ignoring path %s', src ), 'MapLoader' ) + return false + end + local module = require( src .. INFO_FILE ) if not module or not validate( module ) then Log.warn( string.format( 'Couldn\'t load map from %s. Bad format on info file.', src ), 'MapLoader' ) diff --git a/src/map/procedural/MapGenerator.lua b/src/map/procedural/MapGenerator.lua new file mode 100644 index 00000000..89bdd666 --- /dev/null +++ b/src/map/procedural/MapGenerator.lua @@ -0,0 +1,278 @@ +--- +-- This class handles the generation of procedural maps. +-- @module MapGenerator +-- + +-- ------------------------------------------------ +-- Required Modules +-- ------------------------------------------------ + +local Log = require( 'src.util.Log' ) +local ArrayRotation = require( 'src.util.ArrayRotation' ) +local PrefabLoader = require( 'src.map.procedural.PrefabLoader' ) +local ParcelGrid = require( 'src.map.procedural.ParcelGrid' ) +local TileFactory = require( 'src.map.tiles.TileFactory' ) +local WorldObjectFactory = require( 'src.map.worldobjects.WorldObjectFactory' ) + +-- ------------------------------------------------ +-- Module +-- ------------------------------------------------ + +local MapGenerator = {} + +-- ------------------------------------------------ +-- Constants +-- ------------------------------------------------ + +local LAYOUTS_SOURCE_FOLDER = 'res/data/procgen/layouts/' + +local PARCEL_SIZE = require( 'src.constants.PARCEL_SIZE' ) + +-- ------------------------------------------------ +-- Private Variables +-- ------------------------------------------------ + +local layouts = {} + +-- ------------------------------------------------ +-- Private Functions +-- ------------------------------------------------ + +--- +-- Loads parcel layouts from the provided path. +-- @tparam string sourceFolder The path to check. +-- +local function loadLayoutTemplates( sourceFolder ) + local count = 0 + for _, item in ipairs( love.filesystem.getDirectoryItems( sourceFolder )) do + local path = sourceFolder .. item + local layout = love.filesystem.load( path ) + layouts[#layouts + 1] = layout() + + count = count + 1 + Log.debug( string.format( ' %3d. %s', count, item )) + end +end + +-- ------------------------------------------------ +-- Public Functions +-- ------------------------------------------------ + +function MapGenerator.load() + Log.debug( 'Loading parcel layouts:' ) + loadLayoutTemplates( LAYOUTS_SOURCE_FOLDER ) +end + +-- ------------------------------------------------ +-- Constructor +-- ------------------------------------------------ + +function MapGenerator.new() + local self = {} + + -- ------------------------------------------------ + -- Private Attributes + -- ------------------------------------------------ + + -- The actual tile map. + local tileGrid + local width, height + + -- The parcel layout. + local parcelGrid + + -- Spawnpoints. + local spawnpoints = { + allied = {} + } + + -- ------------------------------------------------ + -- Private Methods + -- ------------------------------------------------ + + --- + -- Creates a Tile at the given coordinates. + -- @tparam number x The tile's coordinate along the x-axis. + -- @tparam number y The tile's coordinate along the y-axis. + -- @tparam string tile The tile's id. + -- @treturn Tile The new Tile object. + -- + local function createTile( x, y, tile ) + return TileFactory.create( x, y, tile ) + end + + --- + -- Places the tiles and world objects belonging to this prefab. + -- @tparam Prefab prefab The prefab to place. + -- @tparam number px The starting coordinates along the x-axis for this prefab. + -- @tparam number py The starting coordinates along the y-axis for this prefab. + -- @tparam number rotate The rotation to apply to the prefab before placing it. + -- + local function placePrefab( prefab, px, py, rotate ) + local tiles = prefab:getTiles() + local objects = prefab:getObjects() + + if rotate then + tiles = ArrayRotation.rotate( tiles, rotate ) + objects = ArrayRotation.rotate( objects, rotate ) + end + + for tx = 1, #tiles do + for ty = 1, #tiles[tx] do + tileGrid[tx + px][ty + py] = createTile( tx + px, ty + py, tiles[tx][ty] ) + + -- The object grid can contain empty tiles. + if objects[tx][ty] then + tileGrid[tx + px][ty + py]:addWorldObject( WorldObjectFactory.create( objects[tx][ty] )) + end + end + end + end + + --- + -- Determines a random rotation for a certain parcel layout. + -- @tparam number pw The parcel's width. + -- @tparam number ph The parcel's height. + -- @treturn number The rotation direction [0, 3]. + -- + local function rotateParcels( pw, ph ) + -- Square parcels can be rotated in all directions. + if pw == ph then + return love.math.random( 0, 3 ) + end + + -- Handle rotation for rectangular parcels. + if pw < ph then + return love.math.random() > 0.5 and 1 or 3 + elseif pw > ph then + return love.math.random() > 0.5 and 0 or 2 + end + end + + --- + -- Iterates over the parcel definitions for this map layout and tries to + -- place prefabs for each of them. + -- @tparam table parcels The parcel definitions. + -- + local function fillParcels( parcels ) + for type, definitions in pairs( parcels ) do + Log.debug( string.format( 'Placing %s parcels.', type ), 'MapGenerator' ) + + for _, definition in ipairs( definitions ) do + parcelGrid:addParcels( definition.x, definition.y, definition.w, definition.h, type ) + + local prefab = PrefabLoader.getPrefab( type ) + if prefab then + local rotation = rotateParcels( definition.w, definition.h ) + + -- Get parcel coordinates. + local x = definition.x + local y = definition.y + + -- Place tiles and worldobjects. + placePrefab( prefab, x * PARCEL_SIZE.WIDTH, y * PARCEL_SIZE.HEIGHT, rotation ) + end + end + end + end + + --- + -- Spawns trees in designated parcels. + -- + local function spawnFoliage() + parcelGrid:iterate( function( parcel, x, y ) + if parcel:getType() ~= 'foliage' then + return + end + + local n = parcel:getNeighbourCount() + + local tx, ty = x * PARCEL_SIZE.WIDTH, y * PARCEL_SIZE.HEIGHT + for w = 1, PARCEL_SIZE.WIDTH do + for h = 1, PARCEL_SIZE.HEIGHT do + + -- Increase density based on count of neighbouring foliage tiles. + if love.math.random() < n/10 then + tileGrid[tx + w][ty + h]:addWorldObject( WorldObjectFactory.create( 'worldobject_tree' )) + end + end + end + end) + end + + --- + -- Creates an empty tile grid. + -- @tparam number w The width of the grid in parcels. + -- @tparam number h The height of the grid in parcels. + -- @tparam table The new tile grid. + -- + local function createTileGrid( w, h ) + local tiles = {} + for x = 1, w * PARCEL_SIZE.WIDTH do + tiles[x] = {} + for y = 1, h * PARCEL_SIZE.HEIGHT do + -- TODO Better algorithm for placing ground tiles. + local id = love.math.random() > 0.7 and 'tile_soil' or 'tile_grass' + tiles[x][y] = createTile( x, y, id ) + end + end + return tiles + end + + --- + -- Create spawnpoints. + -- TODO Proper implementation. + -- + local function createSpawnPoints( spawns ) + for _, definition in ipairs( spawns ) do + local x, y = definition.x * PARCEL_SIZE.WIDTH, definition.y * PARCEL_SIZE.HEIGHT + + for w = 0, PARCEL_SIZE.WIDTH-1 do + for h = 0, PARCEL_SIZE.HEIGHT-1 do + spawnpoints.allied[#spawnpoints.allied + 1] = tileGrid[x+w][y+h] + end + end + end + end + + -- ------------------------------------------------ + -- Public Methods + -- ------------------------------------------------ + + function self:init() + -- Select random layout. + local layout = layouts[love.math.random( #layouts )] + + -- Generate empty parcel grid. + parcelGrid = ParcelGrid.new() + parcelGrid:init( layout.dimensions.width, layout.dimensions.height ) + + -- Generate empty tile grid. + width, height = layout.dimensions.width, layout.dimensions.height + tileGrid = createTileGrid( width, height ) + + fillParcels( layout.parcels ) + + parcelGrid:createNeighbours() + + spawnFoliage() + + createSpawnPoints( layout.parcels.spawns ) + end + + function self:getSpawnpoints() + return spawnpoints + end + + function self:getTiles() + return tileGrid + end + + function self:getTileGridDimensions() + return width * PARCEL_SIZE.WIDTH, height * PARCEL_SIZE.HEIGHT + end + + return self +end + +return MapGenerator diff --git a/src/map/procedural/Parcel.lua b/src/map/procedural/Parcel.lua new file mode 100644 index 00000000..b48b764c --- /dev/null +++ b/src/map/procedural/Parcel.lua @@ -0,0 +1,49 @@ +--- +-- Represents a parcel inside of the procedural maps parcel grid and can be +-- used to check for valid spawning points. +-- @module Parcel +-- + +-- ------------------------------------------------ +-- Required Modules +-- ------------------------------------------------ + +local Object = require( 'src.Object' ) + +-- ------------------------------------------------ +-- Module +-- ------------------------------------------------ + +local Parcel = {} + +-- ------------------------------------------------ +-- Constructor +-- ------------------------------------------------ + +function Parcel.new( type ) + local self = Object.new():addInstance( 'Parcel' ) + + local neighbours + + function self:setNeighbours( nneighbours ) + neighbours = nneighbours + end + + function self:getType() + return type + end + + function self:getNeighbourCount() + local count = 0 + for _, neighbour in pairs( neighbours ) do + if neighbour and neighbour:getType() == type then + count = count + 1 + end + end + return count + end + + return self +end + +return Parcel diff --git a/src/map/procedural/ParcelGrid.lua b/src/map/procedural/ParcelGrid.lua new file mode 100644 index 00000000..3a060a11 --- /dev/null +++ b/src/map/procedural/ParcelGrid.lua @@ -0,0 +1,104 @@ +--- +-- @module ParcelGrid +-- + +-- ------------------------------------------------ +-- Required Modules +-- ------------------------------------------------ + +local Object = require( 'src.Object' ) +local Parcel = require( 'src.map.procedural.Parcel' ) + +-- ------------------------------------------------ +-- Module +-- ------------------------------------------------ + +local ParcelGrid = {} + +-- ------------------------------------------------ +-- Constants +-- ------------------------------------------------ + +local DIRECTION = require( 'src.constants.DIRECTION' ) + + +-- ------------------------------------------------ +-- Constructor +-- ------------------------------------------------ + +function ParcelGrid.new() + local self = Object.new():addInstance( 'ParcelGrid' ) + + local parcels + + local function createGrid( w, h ) + local nparcels = {} + for x = 0, w-1 do + for y = 0, h-1 do + nparcels[x] = nparcels[x] or {} + nparcels[x][y] = Parcel.new( 'empty' ) + end + end + return nparcels + end + + function self:init( w, h ) + parcels = createGrid( w, h ) + end + + function self:addParcels( x, y, w, h, type ) + for px = 0, w-1 do + for py = 0, h-1 do + parcels[px+x][py+y] = Parcel.new( type ) + end + end + end + + --- + -- Gives each parcel in the grid a reference to its neighbours. + -- + function self:createNeighbours() + for x = 0, #parcels do + for y = 0, #parcels[x] do + local neighbours = {} + + neighbours[DIRECTION.NORTH] = self:getParcelAt( x , y - 1 ) or false + neighbours[DIRECTION.SOUTH] = self:getParcelAt( x , y + 1 ) or false + neighbours[DIRECTION.NORTH_EAST] = self:getParcelAt( x + 1, y - 1 ) or false + neighbours[DIRECTION.NORTH_WEST] = self:getParcelAt( x - 1, y - 1 ) or false + neighbours[DIRECTION.SOUTH_EAST] = self:getParcelAt( x + 1, y + 1 ) or false + neighbours[DIRECTION.SOUTH_WEST] = self:getParcelAt( x - 1, y + 1 ) or false + neighbours[DIRECTION.EAST] = self:getParcelAt( x + 1, y ) or false + neighbours[DIRECTION.WEST] = self:getParcelAt( x - 1, y ) or false + + parcels[x][y]:setNeighbours( neighbours ) + end + end + end + + --- + -- Returns the Parcel at the given coordinates. + -- @tparam number x The position along the x-axis. + -- @tparam number y The position along the y-axis. + -- @treturn Parcel The Parcel at the given position. + -- + function self:getParcelAt( x, y ) + return parcels[x] and parcels[x][y] + end + + --- + -- Iterates over all tiles and performs the callback function on them. + -- @param callback (function) The operation to perform on each tile. + -- + function self:iterate( callback ) + for x = 0, #parcels do + for y = 0, #parcels[x] do + callback( parcels[x][y], x, y ) + end + end + end + + return self +end + +return ParcelGrid diff --git a/src/map/procedural/Prefab.lua b/src/map/procedural/Prefab.lua new file mode 100644 index 00000000..00aa259c --- /dev/null +++ b/src/map/procedural/Prefab.lua @@ -0,0 +1,39 @@ +-- ------------------------------------------------ +-- Module +-- ------------------------------------------------ + +local Prefab = {} + +function Prefab.new( name, type ) + local self = {} + + local grid = {} + + function self:getName() + return name + end + + function self:getType() + return type + end + + function self:setTiles( ntiles ) + grid.tiles = ntiles + end + + function self:setObjects( nobjects ) + grid.objects = nobjects + end + + function self:getTiles() + return grid.tiles + end + + function self:getObjects() + return grid.objects + end + + return self +end + +return Prefab diff --git a/src/map/procedural/PrefabLoader.lua b/src/map/procedural/PrefabLoader.lua new file mode 100644 index 00000000..c2146940 --- /dev/null +++ b/src/map/procedural/PrefabLoader.lua @@ -0,0 +1,174 @@ +--- +-- This class handles the generation of procedural maps. +-- @module PrefabLoader +-- + +-- ------------------------------------------------ +-- Required Modules +-- ------------------------------------------------ + +local Log = require( 'src.util.Log' ) +local Prefab = require( 'src.map.procedural.Prefab' ) + +-- ------------------------------------------------ +-- Module +-- ------------------------------------------------ + +local PrefabLoader = {} + +-- ------------------------------------------------ +-- Constants +-- ------------------------------------------------ + +local PREFAB_SOURCE_FOLDER = 'res/data/procgen/prefabs/' + +local INFO_FILE = 'info' +local GROUND_LAYER_FILE = 'ground.png' +local OBJECT_LAYER_FILE = 'objects.png' + +local PREFAB_TYPES_ROAD = 'road' +local PREFAB_TYPES_SMALL = 'small' +local PREFAB_TYPES_MEDIUM = 'medium' +local PREFAB_TYPES_LARGE = 'large' +local PREFAB_TYPES_HUGE = 'huge' + +-- ------------------------------------------------ +-- Private Variables +-- ------------------------------------------------ + +local prefabs = {} + +-- ------------------------------------------------ +-- Private Functions +-- ------------------------------------------------ + +--- +-- Tries to map a pixel to a template based on its RGBA values. +-- @tparam table templates Table containing the template to RGBA mappings. +-- @tparam number r The red-component read from the ground layer. +-- @tparam number g The green-component read from the ground layer. +-- @tparam number b The blue-component read from the ground layer. +-- @tparam number a The alpha value read from the ground layer. +-- @treturn string The template id. +-- +-- +local function mapPixels( templates, r, g, b, a ) + local id + for _, template in ipairs( templates ) do + if template.r == r and template.g == g and template.b == b and a == 255 then + id = template.id + break + end + end + return id +end + +--- +-- Iterates over all pixels in the image file and tries to map the colors to +-- a template provided in the info file inside of a 2d array. +-- @tparam table templates Table containing the template to RGBA mappings. +-- @tparam Image img The image containing the layout. +-- @treturn table Grid containing the mapped templates. +-- +local function convertImageToTemplates( templates, img ) + local grid = {} + for x = 1, img:getWidth() do + grid[x] = {} + for y = 1, img:getHeight() do + grid[x][y] = mapPixels( templates, img:getPixel( x - 1, y - 1 )) or false + end + end + return grid +end + +--- +-- Creates a prefab object based on the loaded data from the template files. +-- @tparam table info The data loaded from the info file. +-- @tparam Image ground The image representing the ground layer. +-- @tparam Image objects The image representing the objects layer. +-- @treturn Prefab The newly created Prefab. +-- +local function createPrefab( info, ground, objects ) + local prefab = Prefab.new( info.name, info.type ) + + prefab:setTiles( convertImageToTemplates( info.ground, ground )) + prefab:setObjects( convertImageToTemplates( info.objects, objects )) + + return prefab +end + +--- +-- Loads a prefab template. +-- It'll look for an info file and two images representing the tile and object +-- layers. +-- @tparam string src The path to load the template from. +-- @treturn boolean True if the prefab was loaded correctly. +-- @treturn table The loaded prefabTemplate (only if successful). +-- +local function load( src ) + if not love.filesystem.exists( src .. INFO_FILE .. '.lua' ) then + Log.warn( string.format( 'Can\'t find info file. Ignoring path %s', src ), 'PrefabLoader' ) + return false + end + + local module = require( src .. INFO_FILE ) + if not module then + Log.warn( string.format( 'Couldn\'t load prefab from %s. Bad format on info file.', src ), 'PrefabLoader' ) + return false + end + + local ground = love.image.newImageData( src .. GROUND_LAYER_FILE ) + local objects = love.image.newImageData( src .. OBJECT_LAYER_FILE ) + + return true, createPrefab( module, ground, objects ) +end + +--- +-- Loads prefabs from the provided path. +-- @tparam string sourceFolder The path to check. +-- +local function loadPrefabTemplates( sourceFolder ) + local count = 0 + for _, item in ipairs( love.filesystem.getDirectoryItems( sourceFolder )) do + local path = sourceFolder .. item .. '/' + if love.filesystem.isDirectory( path ) then + local success, prefab = load( path ) + + if success then + prefabs[prefab:getType()][#prefabs[prefab:getType()] + 1] = prefab + + count = count + 1 + Log.debug( string.format( ' %3d. %s', count, prefab:getName() )) + end + end + end +end + +-- ------------------------------------------------ +-- Public Functions +-- ------------------------------------------------ + +--- +-- Loads all prefab templates the game can find. +-- +function PrefabLoader.load() + Log.debug( 'Loading parcel prefabs:' ) + + -- Init prefab tables. + prefabs[PREFAB_TYPES_ROAD] = {} + prefabs[PREFAB_TYPES_SMALL] = {} + prefabs[PREFAB_TYPES_MEDIUM] = {} + prefabs[PREFAB_TYPES_LARGE] = {} + prefabs[PREFAB_TYPES_HUGE] = {} + + loadPrefabTemplates( PREFAB_SOURCE_FOLDER ) +end + +function PrefabLoader.getPrefab( type ) + if not prefabs[type] then + return false + end + return prefabs[type][love.math.random(#prefabs[type])] +end + +return PrefabLoader diff --git a/src/map/procedural/ProceduralMap.lua b/src/map/procedural/ProceduralMap.lua new file mode 100644 index 00000000..07cce3d3 --- /dev/null +++ b/src/map/procedural/ProceduralMap.lua @@ -0,0 +1,214 @@ +local Observable = require( 'src.util.Observable' ) +local WorldObjectFactory = require( 'src.map.worldobjects.WorldObjectFactory' ) +local ItemFactory = require( 'src.items.ItemFactory' ) + +-- ------------------------------------------------ +-- Module +-- ------------------------------------------------ + +local ProceduralMap = {} + +-- ------------------------------------------------ +-- Constants +-- ------------------------------------------------ + +local DIRECTION = require( 'src.constants.DIRECTION' ) + +-- ------------------------------------------------ +-- Constructor +-- ------------------------------------------------ + +function ProceduralMap.new( tiles, width, height ) + local self = Observable.new():addInstance( 'ProceduralMap' ) + + -- ------------------------------------------------ + -- Private Attributes + -- ------------------------------------------------ + + local spawnpoints + + -- ------------------------------------------------ + -- Private Methods + -- ------------------------------------------------ + + --- + -- Gives each tile a reference to its neighbours. + -- + local function addNeighbours() + for x = 1, #tiles do + for y = 1, #tiles[x] do + local neighbours = {} + + neighbours[DIRECTION.NORTH] = self:getTileAt( x , y - 1 ) + neighbours[DIRECTION.SOUTH] = self:getTileAt( x , y + 1 ) + neighbours[DIRECTION.NORTH_EAST] = self:getTileAt( x + 1, y - 1 ) + neighbours[DIRECTION.NORTH_WEST] = self:getTileAt( x - 1, y - 1 ) + neighbours[DIRECTION.SOUTH_EAST] = self:getTileAt( x + 1, y + 1 ) + neighbours[DIRECTION.SOUTH_WEST] = self:getTileAt( x - 1, y + 1 ) + neighbours[DIRECTION.EAST] = self:getTileAt( x + 1, y ) + neighbours[DIRECTION.WEST] = self:getTileAt( x - 1, y ) + + tiles[x][y]:addNeighbours( neighbours ) + end + end + end + + -- ------------------------------------------------ + -- Public Methods + -- ------------------------------------------------ + + --- + -- Initialises the map by creating the Tiles, creating references to the + -- neighbouring tiles and adding WorldObjects. + -- + function self:init( savegame ) + -- TODO Fix savegames + if savegame then + error( 'Savegames are currently borked!' ) + return + end + + addNeighbours() + end + + --- + -- Iterates over all tiles and performs the callback function on them. + -- @param callback (function) The operation to perform on each tile. + -- + function self:iterate( callback ) + for x = 1, width do + for y = 1, height do + callback( tiles[x][y], x, y ) + end + end + end + + --- + -- Randomly searches for a tile on which a player could be spawned. + -- @treturn Tile A tile suitable for spawning. + -- + function self:findSpawnPoint( faction ) + for _ = 1, 2000 do + local x = love.math.random( 1, width ) + local y = love.math.random( 1, height ) + + if faction ~= 'allied' then + local tile = self:getTileAt( x, y ) + if tile:isPassable() and not tile:isOccupied() then + return tile + end + else + local tile = self:getTileAt( x, y ); + for _, spawn in ipairs( spawnpoints[faction] ) do + if tile == spawn and tile:isPassable() and not tile:isOccupied() then + return tile; + end + end + end + end + end + + --- + -- Updates the map. This resets the visibility attribute for all visible + -- tiles and marks them for a drawing update. It also replaces destroyed + -- WorldObjects with their debris types or removes them completely. + -- + function self:update() + for x = 1, #tiles do + for y = 1, #tiles[x] do + local tile = tiles[x][y] + if tile:hasWorldObject() and tile:getWorldObject():isDestroyed() then + -- Create items from the destroyed object. + for _, drop in ipairs( tile:getWorldObject():getDrops() ) do + local id, tries, chance = drop.id, drop.tries, drop.chance + for _ = 1, tries do + if love.math.random( 100 ) < chance then + local item = ItemFactory.createItem( id ) + tile:getInventory():addItem( item ) + end + end + end + + -- If the world object was a container drop the items in it. + if tile:getWorldObject():isContainer() and not tile:getWorldObject():getInventory():isEmpty() then + local items = tile:getWorldObject():getInventory():getItems() + for _, item in pairs( items ) do + tile:getInventory():addItem( item ) + end + end + + -- If the world object has a debris object, place that on the tile. + if tile:getWorldObject():getDebrisID() then + local nobj = WorldObjectFactory.create( tile:getWorldObject():getDebrisID() ) + tile:removeWorldObject() + tile:addWorldObject( nobj ) + else + tile:removeWorldObject() + end + self:publish( 'TILE_UPDATED', tile ) + end + end + end + end + + --- + -- Marks all tiles which have been explored by this faction as dirty. + -- @param faction (string) The faction identifier. + -- + function self:updateExplorationInfo( faction ) + for x = 1, #tiles do + for y = 1, #tiles[x] do + if tiles[x][y]:isExplored( faction ) then + tiles[x][y]:setDirty( true ) + end + end + end + end + + function self:serialize() + local t = { + width = width, + height = height, + tiles = {} + } + + for x = 1, #tiles do + for y = 1, #tiles[x] do + table.insert( t.tiles, tiles[x][y]:serialize() ) + end + end + + return t + end + + -- ------------------------------------------------ + -- Getters + -- ------------------------------------------------ + + --- + -- Returns the Tile at the given coordinates. + -- @param x (number) The position along the x-axis. + -- @param y (number) The position along the y-axis. + -- @return (Tile) The Tile at the given position. + -- + function self:getTileAt( x, y ) + return tiles[x] and tiles[x][y] + end + + --- + -- Returns the map's dimensions. + -- @treturn number The map's width. + -- @treturn number The map's height. + -- + function self:getDimensions() + return width, height + end + + function self:setSpawnpoints( nspawnpoints ) + spawnpoints = nspawnpoints + end + + return self +end + +return ProceduralMap diff --git a/src/ui/CameraHandler.lua b/src/ui/CameraHandler.lua index 01b0863d..4cc54951 100644 --- a/src/ui/CameraHandler.lua +++ b/src/ui/CameraHandler.lua @@ -1,47 +1,82 @@ -local Camera = require('lib.Camera'); -local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) +--- +-- @module CameraHandler +-- -local CameraHandler = {}; +-- ------------------------------------------------ +-- Required Modules +-- ------------------------------------------------ -local CAMERA_TRACKING_SPEED = 10; -local SCROLL_MARGIN = 15; -local SCROLL_SPEED = 10; +local Camera = require( 'lib.Camera' ) -function CameraHandler.new( map ) - local self = Camera.new(); +-- ------------------------------------------------ +-- Module +-- ------------------------------------------------ + +local CameraHandler = {} + +-- ------------------------------------------------ +-- Constants +-- ------------------------------------------------ + +local CAMERA_TRACKING_SPEED = 10 +local SCROLL_MARGIN = 15 +local SCROLL_SPEED = 10 + +-- ------------------------------------------------ +-- Constructor +-- ------------------------------------------------ + +--- +-- Creates a new CameraHandler instance. +-- @tparam number mw The map's width. +-- @tparam number mh The map's height. +-- @tparam number tw The width of the tiles used for map drawing. +-- @tparam number th The height of the tiles used for map drawing. +-- @treturn CameraHandler A new instance of the CameraHandler class. +-- +function CameraHandler.new( mw, mh, tw, th ) + local self = Camera.new() + + -- ------------------------------------------------ + -- Private Attributes + -- ------------------------------------------------ - local tw, th = TexturePacks:getTileDimensions() - local mw, mh = map:getDimensions() local px, py = mw * tw * 0.5, mh * th * 0.5 - local tx, ty = px, py; - local savedX, savedY; - local locked; + local tx, ty = px, py + local locked + + -- ------------------------------------------------ + -- Private Methods + -- ------------------------------------------------ --- -- Linear interpolation between a and b. - -- @param a (number) The current value. - -- @param b (number) The target value. - -- @param t (number) The time value. - -- @return (number) The interpolated value. + -- @tparam number a The current value. + -- @tparam number b The target value. + -- @tparam number t The time value. + -- @treturn number The interpolated value. -- local function lerp( a, b, t ) - return a + ( b - a ) * t; + return a + ( b - a ) * t end + --- + -- Scrolls the camera if the mouse pointer reaches the edges of the screen. + -- local function scroll() - local mx, my = love.mouse.getPosition(); - local x, y = tx, ty; + local mx, my = love.mouse.getPosition() + local x, y = tx, ty if mx < SCROLL_MARGIN then - x = x - SCROLL_SPEED; + x = x - SCROLL_SPEED elseif mx > love.graphics.getWidth() - SCROLL_MARGIN then - x = x + SCROLL_SPEED; + x = x + SCROLL_SPEED end if my < SCROLL_MARGIN then - y = y - SCROLL_SPEED; + y = y - SCROLL_SPEED elseif my > love.graphics.getHeight() - SCROLL_MARGIN then - y = y + SCROLL_SPEED; + y = y + SCROLL_SPEED end -- Clamp the camera to the map dimensions. @@ -49,43 +84,71 @@ function CameraHandler.new( map ) ty = math.max( 0, math.min( y, mh * th )) end + -- ------------------------------------------------ + -- Public Methods + -- ------------------------------------------------ + + --- + -- Updates the camera's scrolling, by lerping the camera's current position + -- to the target position. + -- @tparam number dt The time since the last frame. + -- + -- @see setTargetPosition + -- function self:update( dt ) if not locked then - scroll(); + scroll() end px = lerp( px, math.floor( tx / tw ) * tw, dt * CAMERA_TRACKING_SPEED ) py = lerp( py, math.floor( ty / th ) * th, dt * CAMERA_TRACKING_SPEED ) - self:lookAt( math.floor( px ), math.floor( py )); + self:lookAt( math.floor( px ), math.floor( py )) end - function self:setTargetPosition( dx, dy ) - tx, ty = dx, dy; + --- + -- Locks the camera to prevent scrolling. + -- + function self:lock() + locked = true end - function self:getMousePosition() - return self:mousepos(); + --- + -- Unlocks the camera and re-enables scrolling. + -- + function self:unlock() + locked = false end - function self:storePosition() - savedX, savedY = tx, ty; - end + -- ------------------------------------------------ + -- Getters + -- ------------------------------------------------ - function self:restorePosition() - if savedX and savedY then - tx, ty = savedX, savedY; - savedX, savedY = nil, nil; - end + --- + -- Returns the position of the mouse pointer. + -- @treturn number The x coordinate of the mouse pointer. + -- @treturn number The y coordinate of the mouse pointer. + -- + function self:getMousePosition() + return self:mousepos() end - function self:lock() - locked = true; - end + -- ------------------------------------------------ + -- Setters + -- ------------------------------------------------ - function self:unlock() - locked = false; + --- + -- Sets the target position for the camera. This will not move the camera + -- directly. Use the update function to lerp the current camera position + -- to the target position. + -- @tparam number The new position along the x axis. + -- @tparam number The new position along the y axis. + -- + -- @see update + -- + function self:setTargetPosition( dx, dy ) + tx, ty = dx, dy end - return self; + return self end -return CameraHandler; +return CameraHandler diff --git a/src/ui/MapPainter.lua b/src/ui/MapPainter.lua index 8599ff3d..df768cbf 100644 --- a/src/ui/MapPainter.lua +++ b/src/ui/MapPainter.lua @@ -21,6 +21,12 @@ local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) local MapPainter = {} +-- ------------------------------------------------ +-- Constants +-- ------------------------------------------------ + +local MAX_SPRITES = 16384 -- Enough sprites for a 128*128 map. + -- ------------------------------------------------ -- Constructor -- ------------------------------------------------ @@ -62,10 +68,11 @@ function MapPainter.new() -- Selects a color which to use when a tile is drawn based on its contents. -- @tparam Tile tile The tile to choose a color for. -- @tparam Faction faction The faction to draw for. - -- @tparam Character character The faction's currently active character. -- @treturn table A table containing RGBA values. -- - local function selectTileColor( tile, faction, character ) + local function selectTileColor( tile, faction ) + local character = faction:getCurrentCharacter() + -- Hide unexplored tiles. if not tile:isExplored( faction:getType() ) then return TexturePacks.getColor( 'tile_unexplored' ) @@ -146,7 +153,7 @@ function MapPainter.new() local faction = factions:getPlayerFaction() map:iterate( function( tile, x, y ) if tile:isDirty() then - spritebatch:setColor( selectTileColor( tile, faction, faction:getCurrentCharacter() )) + spritebatch:setColor( selectTileColor( tile, faction )) spritebatch:set( tile:getSpriteID(), selectTileSprite( tile, faction ), x * tw, y * th ) tile:setDirty( false ) end @@ -168,7 +175,7 @@ function MapPainter.new() tileset = TexturePacks.getTileset() tw, th = tileset:getTileDimensions() TexturePacks.setBackgroundColor() - spritebatch = love.graphics.newSpriteBatch( tileset:getSpritesheet(), 10000, 'dynamic' ) + spritebatch = love.graphics.newSpriteBatch( tileset:getSpritesheet(), MAX_SPRITES, 'dynamic' ) initSpritebatch() end diff --git a/src/ui/UserInterface.lua b/src/ui/UserInterface.lua index 6554de91..69d92bcc 100644 --- a/src/ui/UserInterface.lua +++ b/src/ui/UserInterface.lua @@ -9,6 +9,12 @@ local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) local UserInterface = {}; +-- ------------------------------------------------ +-- Constants +-- ------------------------------------------------ + +local ITEM_TYPES = require( 'src.constants.ITEM_TYPES' ) + -- ------------------------------------------------ -- Constructor -- ------------------------------------------------ @@ -32,7 +38,7 @@ function UserInterface.new( game ) -- Draws some information of the tile the mouse is currently hovering over. -- local function inspectTile() - local x, y = tw, love.graphics.getHeight() - th * 5 + local x, y = tw, love.graphics.getHeight() - th * 6 local tile = map:getTileAt( mouseX, mouseY ); if not tile then @@ -51,18 +57,25 @@ function UserInterface.new( game ) end end - local function drawWeaponInfo( weapon ) + local function drawWeaponInfo( inventory, weapon ) if weapon then - local text = Translator.getText( weapon:getID() ); + love.graphics.print( Translator.getText( 'ui_weapon' ), tw, love.graphics.getHeight() - th * 4 ) + love.graphics.print( Translator.getText( weapon:getID() ), tw + font:measureWidth( Translator.getText( 'ui_weapon' )), love.graphics.getHeight() - th * 4 ) + + -- If the weapon is reloadable we show the current ammo in the magazine, + -- the maximum capacity of the magazine and the total amount of ammo + -- on the character. if weapon:isReloadable() then - text = text .. string.format( ' (%d/%d)', weapon:getMagazine():getRounds(), weapon:getMagazine():getCapacity() ) + local magazine = weapon:getMagazine() + local total = inventory:countItems( ITEM_TYPES.AMMO, magazine:getCaliber() ) + + local text = string.format( ' %d/%d (%d)', magazine:getRounds(), magazine:getCapacity(), total ) + love.graphics.print( Translator.getText( 'ui_ammo' ), tw, love.graphics.getHeight() - th * 3 ) + love.graphics.print( text, tw + font:measureWidth( Translator.getText( 'ui_ammo' )), love.graphics.getHeight() - th * 3 ) end - love.graphics.print( Translator.getText( 'ui_weapon' ), tw, love.graphics.getHeight() - th * 3 ) - love.graphics.print( text, tw + font:measureWidth( Translator.getText( 'ui_weapon' )), love.graphics.getHeight() - th * 3 ) love.graphics.print( Translator.getText( 'ui_mode' ), tw, love.graphics.getHeight() - th * 2 ) love.graphics.print( weapon:getAttackMode().name, tw + font:measureWidth( Translator.getText( 'ui_mode' )), love.graphics.getHeight() - th * 2 ) - end end @@ -77,7 +90,7 @@ function UserInterface.new( game ) local function drawActionPoints( character ) local apString = 'AP: ' .. character:getActionPoints(); - love.graphics.print( apString, tw, love.graphics.getHeight() - th * 4 ) + love.graphics.print( apString, tw, love.graphics.getHeight() - th * 5 ) -- Hide the cost display during the turn's execution. if game:getState():instanceOf( 'ExecutionState' ) then @@ -102,11 +115,11 @@ function UserInterface.new( game ) if cost then local costString, costOffset = ' - ' .. cost, font:measureWidth( apString ) TexturePacks.setColor( 'ui_ap_cost' ) - love.graphics.print( costString, tw + costOffset, love.graphics.getHeight() - th * 4 ) + love.graphics.print( costString, tw + costOffset, love.graphics.getHeight() - th * 5 ) local resultString, resultOffset = ' = ' .. character:getActionPoints() - cost, font:measureWidth( apString .. costString ) TexturePacks.setColor( 'ui_ap_cost_result' ) - love.graphics.print( resultString, tw + resultOffset, love.graphics.getHeight() - th * 4 ) + love.graphics.print( resultString, tw + resultOffset, love.graphics.getHeight() - th * 5 ) end TexturePacks.resetColor() end @@ -121,7 +134,7 @@ function UserInterface.new( game ) drawActionPoints( character ); inspectTile(); - drawWeaponInfo( character:getWeapon() ); + drawWeaponInfo( character:getInventory(), character:getWeapon() ) end function self:update() diff --git a/src/ui/elements/Button.lua b/src/ui/elements/Button.lua index 211e4aab..a28b4b2c 100644 --- a/src/ui/elements/Button.lua +++ b/src/ui/elements/Button.lua @@ -11,7 +11,7 @@ local Button = {}; -- Constructor -- ------------------------------------------------ -function Button.new( text, callback ) +function Button.new( text, callback, alignMode ) local self = Object.new():addInstance( 'Button' ); -- ------------------------------------------------ @@ -31,7 +31,7 @@ function Button.new( text, callback ) function self:draw( x, y, w, _ ) TexturePacks.setColor( focus and 'ui_button_hot' or 'ui_button' ) - love.graphics.printf( text, x, y, w, 'center' ) + love.graphics.print( text, x + TexturePacks.getFont():align( alignMode or 'center', text, w ), y ) TexturePacks.resetColor() end diff --git a/src/ui/elements/CharacterSelector.lua b/src/ui/elements/CharacterSelector.lua index cf5bec87..bff278ee 100644 --- a/src/ui/elements/CharacterSelector.lua +++ b/src/ui/elements/CharacterSelector.lua @@ -9,8 +9,10 @@ local Observable = require( 'src.util.Observable' ) local VerticalList = require( 'src.ui.elements.VerticalList' ) local Button = require( 'src.ui.elements.Button' ) +local Label = require( 'src.ui.elements.Label' ) local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) -local Outlines = require( 'src.ui.elements.Outlines' ) +local UIOutlines = require( 'src.ui.elements.UIOutlines' ) +local UIBackground = require( 'src.ui.elements.UIBackground' ) local Translator = require( 'src.util.Translator' ) -- ------------------------------------------------ @@ -23,8 +25,7 @@ local CharacterSelector = {} -- Constants -- ------------------------------------------------ -local SCREEN_WIDTH = 10 -local FIELD_WIDTH = 10 +local UI_GRID_WIDTH = 10 -- ------------------------------------------------ -- Constructor @@ -35,54 +36,78 @@ function CharacterSelector.new() local faction local verticalList + local header local font + + local background local outlines local tw, th + local x, y + + -- ------------------------------------------------ + -- Private Methods + -- ------------------------------------------------ + + --- + -- Generates the outlines for this screen. + -- + local function generateOutlines( w, h ) + outlines = UIOutlines.new( x, y, 0, 0, w, h ) + + -- Horizontal borders. + for ox = 0, w-1 do + outlines:add( ox, 0 ) -- Top + outlines:add( ox, 2 ) -- Top + outlines:add( ox, h-1 ) -- Bottom + end - local function createOutlines( w, h ) - for x = 0, w - 1 do - for y = 0, h - 1 do - if x == 0 or x == (w - 1) or y == 0 or y == (h - 1) then - outlines:add( x, y ) - end - if y == 2 then - outlines:add( x, y ) - end - end + -- Vertical outlines. + for oy = 0, h-1 do + outlines:add( 0, oy ) -- Left + outlines:add( w-1, oy ) -- Right end + + outlines:refresh() end local function createCharacterButton( character ) local function callback() self:publish( 'CHANGED_CHARACTER', character ) end - return Button.new( character:getName(), callback ) + return Button.new( character:getName(), callback, 'left' ) + end + + local function createCharacterList() + verticalList = VerticalList.new( tw, 3 * th, (UI_GRID_WIDTH-2) * tw, font:getGlyphHeight() ) + faction:iterate( function( character ) + verticalList:addElement( createCharacterButton( character )) + end) end + -- ------------------------------------------------ + -- Public Methods + -- ------------------------------------------------ + function self:init( nfaction ) faction = nfaction font = TexturePacks.getFont() + x, y = 0, 0 tw, th = TexturePacks.getTileset():getTileDimensions() - verticalList = VerticalList.new( 0, 3 * th, FIELD_WIDTH * tw, font:getGlyphHeight() ) - faction:iterate( function( character ) - verticalList:addElement( createCharacterButton( character )) - end) + createCharacterList() - outlines = Outlines.new() - createOutlines( SCREEN_WIDTH, 4 + verticalList:getElementCount() ) - outlines:refresh() - end + background = UIBackground.new( x, y, 0, 0, UI_GRID_WIDTH, 4 + verticalList:getElementCount() ) - function self:draw() - TexturePacks.setColor( 'sys_background' ) - love.graphics.rectangle( 'fill', 0, 0, FIELD_WIDTH * tw, (4 + verticalList:getElementCount()) * th ) - TexturePacks.resetColor() + generateOutlines( UI_GRID_WIDTH, 4 + verticalList:getElementCount() ) - love.graphics.printf( Translator.getText( 'ui_stalkers' ), tw, th, (FIELD_WIDTH-2) * tw, 'center' ) + header = Label.new( Translator.getText( 'ui_stalkers' ), 'ui_label', 'center' ) + end - outlines:draw( 0, 0 ) + function self:draw() + background:draw() + outlines:draw() + header:draw( tw, th, (UI_GRID_WIDTH-2) * tw, 'center' ) verticalList:draw() end diff --git a/src/ui/elements/HealAllSelector.lua b/src/ui/elements/HealAllSelector.lua deleted file mode 100644 index 9dd289d9..00000000 --- a/src/ui/elements/HealAllSelector.lua +++ /dev/null @@ -1,101 +0,0 @@ ---- --- @module HealAllSelector --- - --- ------------------------------------------------ --- Required Modules --- ------------------------------------------------ - -local Observable = require( 'src.util.Observable' ) -local VerticalList = require( 'src.ui.elements.VerticalList' ) -local Button = require( 'src.ui.elements.Button' ) -local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) -local Outlines = require( 'src.ui.elements.Outlines' ) -local Translator = require( 'src.util.Translator' ) - --- ------------------------------------------------ --- Module --- ------------------------------------------------ - -local HealAllSelector = {} - --- ------------------------------------------------ --- Constants --- ------------------------------------------------ - -local SCREEN_WIDTH = 10 -local SCREEN_HEIGHT = 3 -local FIELD_WIDTH = 10 - --- ------------------------------------------------ --- Constructor --- ------------------------------------------------ - -function HealAllSelector.new() - local self = Observable.new():addInstance( 'HealAllSelector' ) - - local verticalList - local font - local outlines - local tw, th - local px, py - - local function createOutlines( w, h ) - for x = 0, w - 1 do - for y = 0, h - 1 do - if x == 0 or x == (w - 1) or y == 0 or y == (h - 1) then - outlines:add( x, y ) - end - end - end - end - - local function createButton() - local function callback() - self:publish( 'HEAL_CHARACTERS' ) - end - return Button.new( Translator.getText( 'ui_heal_all' ), callback ) - end - - function self:init() - font = TexturePacks.getFont() - tw, th = TexturePacks.getTileset():getTileDimensions() - px, py = love.graphics.getWidth() - FIELD_WIDTH * tw, love.graphics.getHeight() - SCREEN_HEIGHT * th - - verticalList = VerticalList.new( px, py + th, FIELD_WIDTH * tw, font:getGlyphHeight() ) - verticalList:addElement( createButton() ) - - outlines = Outlines.new() - createOutlines( SCREEN_WIDTH, SCREEN_HEIGHT ) - outlines:refresh() - end - - function self:draw() - TexturePacks.setColor( 'sys_background' ) - love.graphics.rectangle( 'fill', px, py, FIELD_WIDTH * tw, SCREEN_HEIGHT * th ) - TexturePacks.resetColor() - - outlines:draw( px, py ) - verticalList:draw() - end - - function self:update() - verticalList:update() - end - - function self:keypressed( _, scancode ) - verticalList:keypressed( _, scancode ) - end - - function self:mousemoved() - verticalList:mousemoved() - end - - function self:mousereleased() - verticalList:mousereleased() - end - - return self -end - -return HealAllSelector diff --git a/src/ui/elements/HorizontalList.lua b/src/ui/elements/HorizontalList.lua index 51307908..73c6586e 100644 --- a/src/ui/elements/HorizontalList.lua +++ b/src/ui/elements/HorizontalList.lua @@ -22,12 +22,11 @@ function HorizontalList.new( x, y, itemW, itemH ) return; end - self:unsetCursor(); - local elements = self:getElements(); local w = #elements * itemW; local ox = x - w * 0.5; + -- Check if mouse is over any elements. for i = 1, #elements do elements[i]:update( ox + (i-1) * itemW, y, itemW, itemH ); @@ -35,6 +34,10 @@ function HorizontalList.new( x, y, itemW, itemH ) self:setCursor( i ); end end + + -- If the mouse isn't over any elements we restore the original cursor. + local cursor = self:getCursor() + elements[cursor]:setFocus( true ) end function self:draw() @@ -71,11 +74,6 @@ function HorizontalList.new( x, y, itemW, itemH ) function self:deactivateMouse() love.mouse.setVisible( false ); - - local elements = self:getElements(); - for i = 1, #elements do - elements[i]:setFocus( false ); - end end function self:mousemoved() diff --git a/src/ui/elements/Label.lua b/src/ui/elements/Label.lua new file mode 100644 index 00000000..f21d0107 --- /dev/null +++ b/src/ui/elements/Label.lua @@ -0,0 +1,34 @@ +--- +-- @module Label +-- + +-- ------------------------------------------------ +-- Required Modules +-- ------------------------------------------------ + +local Object = require( 'src.Object' ) +local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) + +-- ------------------------------------------------ +-- Module +-- ------------------------------------------------ + +local Label = {} + +-- ------------------------------------------------ +-- Constructor +-- ------------------------------------------------ + +function Label.new( text, color, align ) + local self = Object.new():addInstance( 'Label' ) + + function self:draw( x, y, w, _ ) + TexturePacks.setColor( color ) + love.graphics.print( text, x + TexturePacks.getFont():align( align or 'left', text, w ), y ) + TexturePacks.resetColor() + end + + return self +end + +return Label diff --git a/src/ui/elements/NextMissionSelector.lua b/src/ui/elements/NextMissionSelector.lua index d3df5476..ecc9bebd 100644 --- a/src/ui/elements/NextMissionSelector.lua +++ b/src/ui/elements/NextMissionSelector.lua @@ -10,8 +10,10 @@ local Observable = require( 'src.util.Observable' ) local VerticalList = require( 'src.ui.elements.VerticalList' ) local Button = require( 'src.ui.elements.Button' ) local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) -local Outlines = require( 'src.ui.elements.Outlines' ) +local UIOutlines = require( 'src.ui.elements.UIOutlines' ) +local UIBackground = require( 'src.ui.elements.UIBackground' ) local Translator = require( 'src.util.Translator' ) +local GridHelper = require( 'src.util.GridHelper' ) -- ------------------------------------------------ -- Module @@ -23,9 +25,8 @@ local NextMissionSelector = {} -- Constants -- ------------------------------------------------ -local SCREEN_WIDTH = 10 -local SCREEN_HEIGHT = 3 -local FIELD_WIDTH = 10 +local UI_GRID_WIDTH = 10 +local UI_GRID_HEIGHT = 3 -- ------------------------------------------------ -- Constructor @@ -36,17 +37,35 @@ function NextMissionSelector.new() local verticalList local font + + local background local outlines local tw, th + local x, y + + -- ------------------------------------------------ + -- Private Methods + -- ------------------------------------------------ + + --- + -- Generates the outlines for this screen. + -- + local function generateOutlines() + outlines = UIOutlines.new( x, y, 0, 0, UI_GRID_WIDTH, UI_GRID_HEIGHT ) + + -- Horizontal borders. + for ox = 0, UI_GRID_WIDTH-1 do + outlines:add( ox, 0 ) -- Top + outlines:add( ox, UI_GRID_HEIGHT-1 ) -- Bottom + end - local function createOutlines( w, h ) - for x = 0, w - 1 do - for y = 0, h - 1 do - if x == 0 or x == (w - 1) or y == 0 or y == (h - 1) then - outlines:add( x, y ) - end - end + -- Vertical outlines. + for oy = 0, UI_GRID_HEIGHT-1 do + outlines:add( 0, oy ) -- Left + outlines:add( UI_GRID_WIDTH-1, oy ) -- Right end + + outlines:refresh() end local function createButton() @@ -57,23 +76,22 @@ function NextMissionSelector.new() end function self:init() + local sw, _ = GridHelper.getScreenGridDimensions() + x, y = sw - UI_GRID_WIDTH, 0 + font = TexturePacks.getFont() tw, th = TexturePacks.getTileset():getTileDimensions() - verticalList = VerticalList.new( love.graphics.getWidth() - FIELD_WIDTH * tw, th, FIELD_WIDTH * tw, font:getGlyphHeight() ) + verticalList = VerticalList.new( x * tw, (y+1) * th, UI_GRID_WIDTH * tw, font:getGlyphHeight() ) verticalList:addElement( createButton() ) - outlines = Outlines.new() - createOutlines( SCREEN_WIDTH, SCREEN_HEIGHT ) - outlines:refresh() + background = UIBackground.new( x, y, 0, 0, UI_GRID_WIDTH, UI_GRID_WIDTH ) + generateOutlines() end function self:draw() - TexturePacks.setColor( 'sys_background' ) - love.graphics.rectangle( 'fill', love.graphics.getWidth() - FIELD_WIDTH * tw, 0, FIELD_WIDTH * tw, 3 * th ) - TexturePacks.resetColor() - - outlines:draw( love.graphics.getWidth() - FIELD_WIDTH * tw, 0 ) + background:draw() + outlines:draw() verticalList:draw() end diff --git a/src/ui/elements/UIBackground.lua b/src/ui/elements/UIBackground.lua new file mode 100644 index 00000000..f3a7e9aa --- /dev/null +++ b/src/ui/elements/UIBackground.lua @@ -0,0 +1,79 @@ +--- +-- Creates an opaque background of a certain height and width. +-- +-- @module UIBackground +-- + +-- ------------------------------------------------ +-- Required Modules +-- ------------------------------------------------ + +local UIElement = require( 'src.ui.elements.UIElement' ) +local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) + +-- ------------------------------------------------ +-- Module +-- ------------------------------------------------ + +local UIBackground = {} + +-- ------------------------------------------------ +-- Constructor +-- ------------------------------------------------ + +--- +-- Creates a new UIBackground. +-- +-- All of the coordinates should be grid aligned and independent from actual +-- pixel represenations. +-- +-- @tparam number ox The origin along the x-axis. +-- @tparam number oy The origin along the y-axis. +-- @tparam number rx The relative coordinate along the x-axis. +-- @tparam number ry The relative coordinate along the y-axis. +-- @tparam number w The width of this element. +-- @tparam number h The height of this element. +-- +function UIBackground.new( px, py, rx, ry, w, h ) + local self = UIElement.new( px, py, rx, ry, w, h ):addInstance( 'UIBackground' ) + + -- ------------------------------------------------ + -- Private Attributes + -- ------------------------------------------------ + + local color = 'sys_background' + + -- ------------------------------------------------ + -- Public Methods + -- ------------------------------------------------ + + --- + -- Initialises the UIBackground object. + -- @tparam string ncolor The color ID to use for drawing the background. + -- + function self:init( ncolor ) + color = ncolor + end + + --- + -- Draws the background starting at the specified position. + -- + function self:draw() + local tw, th = TexturePacks.getTileDimensions() + TexturePacks.setColor( color ) + love.graphics.rectangle( 'fill', self.ax * tw, self.ay * th, self.w * tw, self.h * th ) + TexturePacks.resetColor() + end + + --- + -- Changes the color used for drawing the background. + -- @tparam string ncolor The color ID to use for drawing the background. + -- + function self:setColor( ncolor ) + color = ncolor + end + + return self +end + +return UIBackground diff --git a/src/ui/elements/UIButton.lua b/src/ui/elements/UIButton.lua new file mode 100644 index 00000000..188b8ef9 --- /dev/null +++ b/src/ui/elements/UIButton.lua @@ -0,0 +1,46 @@ +--- +-- @module UIButton +-- + +-- ------------------------------------------------ +-- Required Modules +-- ------------------------------------------------ + +local UIElement = require( 'src.ui.elements.UIElement' ) +local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) + +-- ------------------------------------------------ +-- Module +-- ------------------------------------------------ + +local UIButton = {} + +function UIButton.new( px, py, x, y, w, h ) + local self = UIElement.new( px, py, x, y, w, h ):addInstance( 'UIButton' ) + + local tileset + local icon + local callback + + function self:init( tileID, ncallback ) + tileset = TexturePacks.getTileset() + icon = TexturePacks.getSprite( tileID ) + + callback = ncallback + end + + function self:draw() + local tw, th = TexturePacks.getTileDimensions() + TexturePacks.setColor( self:isMouseOver() and 'ui_button_hot' or 'ui_button' ) + love.graphics.draw( tileset:getSpritesheet(), icon, self.ax * tw, self.ay * th ) + TexturePacks.resetColor() + end + + function self:activate() + callback() + end + + return self +end + +return UIButton diff --git a/src/ui/elements/UICopyrightFooter.lua b/src/ui/elements/UICopyrightFooter.lua index d98d3710..a81e4c36 100644 --- a/src/ui/elements/UICopyrightFooter.lua +++ b/src/ui/elements/UICopyrightFooter.lua @@ -8,6 +8,7 @@ local Object = require( 'src.Object' ) local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) +local GridHelper = require( 'src.util.GridHelper' ) -- ------------------------------------------------ -- Module @@ -31,10 +32,12 @@ function UICopyrightFooter.new() function self:draw() local font = TexturePacks.getFont() - local sw, sh = love.graphics.getDimensions() + local tw, th = TexturePacks.getTileDimensions() + local sw, sh = GridHelper.getScreenGridDimensions() + TexturePacks.setColor( 'ui_text_dim' ) - love.graphics.print( VERSION_STRING, sw - font:measureWidth( VERSION_STRING ), sh - font:getGlyphHeight() ) - love.graphics.print( COPYRIGHT_STRING, 0, sh - font:getGlyphHeight() ) + love.graphics.print( VERSION_STRING, sw*tw - font:measureWidth( VERSION_STRING ), sh*th - font:getGlyphHeight() ) + love.graphics.print( COPYRIGHT_STRING, 0, sh*th - font:getGlyphHeight() ) TexturePacks.resetColor() end diff --git a/src/ui/elements/UIElement.lua b/src/ui/elements/UIElement.lua new file mode 100644 index 00000000..c55d2f25 --- /dev/null +++ b/src/ui/elements/UIElement.lua @@ -0,0 +1,90 @@ +--- +-- The parent class for all UI elements. +-- @module UIElement +-- + +-- ------------------------------------------------ +-- Required Modules +-- ------------------------------------------------ + +local Observable = require( 'src.util.Observable' ) +local GridHelper = require( 'src.util.GridHelper' ) + +-- ------------------------------------------------ +-- Module +-- ------------------------------------------------ + +local UIElement = {} + +-- ------------------------------------------------ +-- Constructor +-- ------------------------------------------------ + +--- +-- Creates a new UIElement. +-- +-- All of the coordinates should be grid aligned and independent from actual +-- pixel represenations. +-- +-- @tparam number ox The origin along the x-axis. +-- @tparam number oy The origin along the y-axis. +-- @tparam number rx The relative coordinate along the x-axis. +-- @tparam number ry The relative coordinate along the y-axis. +-- @tparam number w The width of this element. +-- @tparam number h The height of this element. +-- +function UIElement.new( ox, oy, rx, ry, w, h ) + local self = Observable.new():addInstance( 'UIElement' ) + + -- ------------------------------------------------ + -- Public Attributes + -- + -- These should never be accessed directly from + -- outside of this class or any subclasses. Use + -- setters and getters for that purpose! + -- ------------------------------------------------ + + -- Origin (parent coordinates). + self.ox = ox + self.oy = oy + + -- Coordinates relative to the origin. + self.rx = rx + self.ry = ry + + -- Absolute coordinates. + self.ax = ox+rx + self.ay = oy+ry + + -- Dimensions. + self.w = w + self.h = h + + -- ------------------------------------------------ + -- Public Methods + -- ------------------------------------------------ + + function self:setOrigin( nx, ny ) + self.ox = nx + self.oy = ny + self.ax = self.ox + self.rx + self.ay = self.oy + self.ry + end + + --- + -- Checks wether or not the mouse cursor is over this element. + -- @treturn boolean True if the mouse is within this element's bounds. + -- + function self:isMouseOver() + local mx, my = love.mouse.getPosition() + local gx, gy = GridHelper.pixelsToGrid( mx, my ) + return gx >= self.ox + self.rx + and gx < self.ox + self.rx + self.w + and gy >= self.oy + self.ry + and gy < self.oy + self.ry + self.h + end + + return self +end + +return UIElement diff --git a/src/ui/elements/UILabel.lua b/src/ui/elements/UILabel.lua new file mode 100644 index 00000000..8d88fdfe --- /dev/null +++ b/src/ui/elements/UILabel.lua @@ -0,0 +1,51 @@ +--- +-- @module UILabel +-- + +-- ------------------------------------------------ +-- Required Modules +-- ------------------------------------------------ + +local UIElement = require( 'src.ui.elements.UIElement' ) +local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) +local Translator = require( 'src.util.Translator' ) + +-- ------------------------------------------------ +-- Module +-- ------------------------------------------------ + +local UILabel = {} + +-- ------------------------------------------------ +-- Constructor +-- ------------------------------------------------ + +function UILabel.new( px, py, x, y, w, h, text, color ) + local self = UIElement.new( px, py, x, y, w, h ):addInstance( 'UILabel' ) + + color = color or 'sys_reset' + + local tw, th = TexturePacks.getTileDimensions() + + function self:draw() + TexturePacks.setColor( color ) + love.graphics.print( text, self.ax * tw, self.ay * th ) + TexturePacks.resetColor() + end + + function self:setText( ntextID ) + text = Translator.getText( ntextID ) + end + + function self:setColor( ncolor ) + color = ncolor + end + + function self:setUpper( nupper ) + text = nupper and string.upper( text ) or text + end + + return self +end + +return UILabel diff --git a/src/ui/elements/Outlines.lua b/src/ui/elements/UIOutlines.lua similarity index 88% rename from src/ui/elements/Outlines.lua rename to src/ui/elements/UIOutlines.lua index fb07338c..3b167e0d 100644 --- a/src/ui/elements/Outlines.lua +++ b/src/ui/elements/UIOutlines.lua @@ -1,31 +1,39 @@ --- -- Creates outlines for UI elements with correctly drawn intersections. --- @module Outlines +-- +-- Make sure to always start the outline grid at coordinates [x=0, y=0], because +-- the draw function uses an offset to determine where to start drawing. +-- If the grid starts at coordinates [x=1, y=1] the outlines will be drawn in +-- the wrong place. +-- +-- @module UIOutlines -- -- ------------------------------------------------ -- Required Modules -- ------------------------------------------------ -local Object = require( 'src.Object' ) +local UIElement = require( 'src.ui.elements.UIElement' ) local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) -- ------------------------------------------------ -- Module -- ------------------------------------------------ -local Outlines = {} +local UIOutlines = {} -- ------------------------------------------------ -- Constructor -- ------------------------------------------------ --- --- Creates a new Outlines instance. --- @treturn Outlines The new instance. +-- Creates a new UIOutlines instance. +-- @tparam number ox The x–coordinate at which to start drawing the outlines. +-- @tparam number oy The y–coordinate at which to start drawing the outlines. +-- @treturn UIOutlines The new instance. -- -function Outlines.new() - local self = Object.new():addInstance( 'Outlines' ) +function UIOutlines.new( px, py, ox, oy, w, h ) + local self = UIElement.new( px, py, ox, oy, w, h ):addInstance( 'UIOutlines' ) -- ------------------------------------------------ -- Private Variables @@ -158,14 +166,12 @@ function Outlines.new() --- -- Draws the outlines at the specified position. - -- @tparam number px The x coordinate to draw the outline grid from. - -- @tparam number py The y coordinate to draw the outline grid from. -- - function self:draw( px, py ) + function self:draw() TexturePacks.setColor( 'ui_outlines' ) for x, line in pairs( grid ) do for y, sprite in pairs( line ) do - love.graphics.draw( tileset:getSpritesheet(), sprite, px + x * tw, py + y * th ) + love.graphics.draw( tileset:getSpritesheet(), sprite, (self.ax+x) * tw, (self.ay+y) * th ) end end TexturePacks.resetColor() @@ -174,4 +180,4 @@ function Outlines.new() return self end -return Outlines +return UIOutlines diff --git a/src/ui/elements/UIScrollArea.lua b/src/ui/elements/UIScrollArea.lua new file mode 100644 index 00000000..ad6912b3 --- /dev/null +++ b/src/ui/elements/UIScrollArea.lua @@ -0,0 +1,153 @@ +--- +-- @module UIScrollArea +-- + +-- ------------------------------------------------ +-- Required Modules +-- ------------------------------------------------ + +local UIElement = require( 'src.ui.elements.UIElement' ) +local UIButton = require( 'src.ui.elements.UIButton' ) +local UIScrollbar = require( 'src.ui.elements.UIScrollbar' ) +local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) + +-- ------------------------------------------------ +-- Module +-- ------------------------------------------------ + +local UIScrollArea = {} + +function UIScrollArea.new( px, py, x, y, w, h ) + local self = UIElement.new( px, py, x, y, w, h ):addInstance( 'UIScrollArea' ) + + -- ------------------------------------------------ + -- Private Attributes + -- ------------------------------------------------ + + local text + local font + local offset + local scrollAreaHeight + + local scrollable + + local upButton + local downButton + local scrollbar + + -- ------------------------------------------------ + -- Private Methods + -- ------------------------------------------------ + + local function calculateContentHeight() + return text:getHeight() / TexturePacks.getFont():getGlyphHeight() + end + + -- ------------------------------------------------ + -- Public Method + -- ------------------------------------------------ + + function self:init( textString ) + font = TexturePacks.getFont():get() + text = love.graphics.newText( font ) + offset = 0 + + self:setText( textString, self.w, self.h ) + + if scrollable then + + -- The height of the scroll content in grid space. + local contentHeight = calculateContentHeight() + + -- The max offset is calculated by taking the height of the scroll area + -- the text height (transformed to grid space). This will stop the scroll + -- offset as soon as the last line hits the bottom of the scroll area. + scrollAreaHeight = contentHeight - self.h + + -- The up button is placed in the top right corner of the scroll area. + upButton = UIButton.new( self.ax, self.ay, self.w-1, 0, 1, 1 ) + upButton:init( 'ui_scroll_area_up', function() self:scroll(-1) end ) + + downButton = UIButton.new( self.ax, self.ay, self.w-1, self.h-1, 1, 1 ) + downButton:init( 'ui_scroll_area_down', function() self:scroll(1) end ) + + -- Scroll bar is positioned between the buttons. + scrollbar = UIScrollbar.new( self.ax, self.ay, self.w-1, 1, 1, self.h-2, 'ui_scroll_area_down' ) + scrollbar:init( self.h, contentHeight, scrollAreaHeight, function( noffset ) self:scroll( noffset ) end ) + end + end + + function self:draw() + local tw, th = TexturePacks.getTileDimensions() + + love.graphics.setScissor( self.ax * tw, self.ay * th, self.w * tw, self.h * th ) + love.graphics.draw( text, self.ax * tw, (self.ay-offset) * th ) + love.graphics.setScissor() + + if scrollable then + upButton:draw() + downButton:draw() + scrollbar:draw() + end + end + + function self:scroll( dir ) + if not scrollable then + return + end + + offset = offset + dir + offset = math.max( 0, math.min( offset, scrollAreaHeight )) + + scrollbar:scroll( offset ) + end + + function self:setPosition( nx, ny ) + self.ox = nx + self.oy = ny + + if not scrollable then + return + end + + local rx, ry = self:getRelativePosition() + upButton:setOrigin( nx+rx, ny+ry ) + downButton:setOrigin( nx+rx, ny+ry ) + scrollbar:setOrigin( nx+rx, ny+ry ) + end + + function self:setText( textString, gw, gh ) + local tw, _ = TexturePacks.getTileDimensions() + + -- The text wrap is one smaller than the scroll area to provide space + -- for the scrollbar. + text:setf( textString, gw * tw, 'left' ) + + -- If the scroll content is bigger than the viewport we add scrollbars. + local textGridHeight = calculateContentHeight() + if textGridHeight > gh then + text:setf( textString, (gw-1) * tw, 'left' ) + scrollable = true + else + scrollable = false + end + end + + function self:mousepressed( mx, my ) + if not scrollable then + return + end + + if upButton:isMouseOver() then + upButton:activate() + elseif downButton:isMouseOver() then + downButton:activate() + elseif scrollbar:isMouseOver() then + scrollbar:mousepressed( mx, my ) + end + end + + return self +end + +return UIScrollArea diff --git a/src/ui/elements/UIScrollbar.lua b/src/ui/elements/UIScrollbar.lua new file mode 100644 index 00000000..52b0ca1b --- /dev/null +++ b/src/ui/elements/UIScrollbar.lua @@ -0,0 +1,127 @@ +--- +-- @module UIScrollbar +-- + +-- ------------------------------------------------ +-- Required Modules +-- ------------------------------------------------ + +local UIElement = require( 'src.ui.elements.UIElement' ) +local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) + +-- ------------------------------------------------ +-- Module +-- ------------------------------------------------ + +local UIScrollbar = {} + +-- ------------------------------------------------ +-- Constants +-- ------------------------------------------------ + +local MIN_CURSOR_HEIGHT = 1 + +-- ------------------------------------------------ +-- Constructor +-- ------------------------------------------------ + +function UIScrollbar.new( px, py, x, y, w, h ) + local self = UIElement.new( px, py, x, y, w, h ):addInstance( 'UIScrollbar' ) + + -- ------------------------------------------------ + -- Private attributes + -- ------------------------------------------------ + + local tileset = TexturePacks.getTileset() + local cursor = TexturePacks.getSprite( 'ui_scrollbar_cursor' ) + local element = TexturePacks.getSprite( 'ui_scrollbar_element' ) + + local viewportHeight + local contentHeight + + local viewportContentRatio + + local cursorHeight + local barScrollableHeight + local cursorPosition + + local maxOffset + + local callback + + -- ------------------------------------------------ + -- Private Methods + -- ------------------------------------------------ + + local function calculateViewportContentRatio() + return viewportHeight / contentHeight + end + + local function calculateCursorHeight() + return math.min( math.max( self.h * viewportContentRatio, MIN_CURSOR_HEIGHT ), self.h ) + end + + local function calculateCursorPosition( offset ) + return (offset / maxOffset) * barScrollableHeight + end + + -- ------------------------------------------------ + -- Public Methods + -- ------------------------------------------------ + + --- + -- @tparam number maxOffset The maximum offset the scroll content can have. + -- + function self:init( nviewportHeight, ncontentHeight, nmaxOffset, ncallback ) + -- Initialise variables. + viewportHeight = nviewportHeight + contentHeight = ncontentHeight + maxOffset = nmaxOffset + callback = ncallback + + -- The ratio between the viewport and the content to display. + viewportContentRatio = calculateViewportContentRatio() + + cursorHeight = calculateCursorHeight() + barScrollableHeight = self.h - cursorHeight + + cursorPosition = calculateCursorPosition( 0 ) + end + + function self:draw() + local tw, th = TexturePacks.getTileDimensions() + + -- Draw the bar. + for oy = 0, self.h-1 do + TexturePacks.setColor( 'ui_scrollbar_element' ) + love.graphics.draw( tileset:getSpritesheet(), element, self.ax * tw, (self.ay+oy) * th ) + end + + -- Draw the cursor. + for oy = 0, cursorHeight-1 do + TexturePacks.setColor( 'ui_scrollbar_cursor' ) + love.graphics.draw( tileset:getSpritesheet(), cursor, self.ax * tw, math.ceil((self.ay+oy+cursorPosition) * th )) + end + + TexturePacks.resetColor() + end + + --- + -- @tparam number offset The offset of the scroll content [-n, 0]. + -- + function self:scroll( offset ) + cursorPosition = calculateCursorPosition( offset ) + end + + function self:mousepressed( _, my ) + if my < self.ay+cursorPosition+(cursorHeight*0.5) then + callback( -4 ) + elseif my > self.ay+cursorPosition+(cursorHeight*0.5) then + callback( 4 ) + end + end + + return self +end + +return UIScrollbar diff --git a/src/ui/elements/UITranslatedLabel.lua b/src/ui/elements/UITranslatedLabel.lua new file mode 100644 index 00000000..1578d199 --- /dev/null +++ b/src/ui/elements/UITranslatedLabel.lua @@ -0,0 +1,32 @@ +--- +-- @module UITranslatedLabel +-- + +-- ------------------------------------------------ +-- Required Modules +-- ------------------------------------------------ + +local UILabel = require( 'src.ui.elements.UILabel' ) +local Translator = require( 'src.util.Translator' ) + +-- ------------------------------------------------ +-- Module +-- ------------------------------------------------ + +local UITranslatedLabel = {} + +-- ------------------------------------------------ +-- Constructor +-- ------------------------------------------------ + +function UITranslatedLabel.new( px, py, x, y, w, h, textID, color ) + local self = UILabel.new( px, py, x, y, w, h, Translator.getText( textID ), color ):addInstance( 'UITranslatedLabel' ) + + function self:setTextID( ntextID ) + self:setText( Translator.getText( ntextID )) + end + + return self +end + +return UITranslatedLabel diff --git a/src/ui/elements/VerticalList.lua b/src/ui/elements/VerticalList.lua index 96469256..7582af07 100644 --- a/src/ui/elements/VerticalList.lua +++ b/src/ui/elements/VerticalList.lua @@ -22,8 +22,6 @@ function VerticalList.new( x, y, itemW, itemH ) return; end - self:unsetCursor() - local elements = self:getElements(); for i = 1, #elements do elements[i]:update( x, y + (i-1) * itemH, itemW, itemH ); @@ -31,6 +29,10 @@ function VerticalList.new( x, y, itemW, itemH ) self:setCursor( i ); end end + + -- If the mouse isn't over any elements we restore the original cursor. + local cursor = self:getCursor() + elements[cursor]:setFocus( true ) end function self:draw() @@ -64,11 +66,6 @@ function VerticalList.new( x, y, itemW, itemH ) function self:deactivateMouse() love.mouse.setVisible( false ); - - local elements = self:getElements(); - for i = 1, #elements do - elements[i]:setFocus( false ); - end end function self:mousemoved() diff --git a/src/ui/elements/inventory/UIEquipmentList.lua b/src/ui/elements/inventory/UIEquipmentList.lua new file mode 100644 index 00000000..192aa2b2 --- /dev/null +++ b/src/ui/elements/inventory/UIEquipmentList.lua @@ -0,0 +1,162 @@ +--- +-- @module UIEquipmentList +-- + +-- ------------------------------------------------ +-- Required Modules +-- ------------------------------------------------ + +local UIElement = require( 'src.ui.elements.UIElement' ) +local UIEquipmentSlot = require( 'src.ui.elements.inventory.UIEquipmentSlot' ) + +-- ------------------------------------------------ +-- Module +-- ------------------------------------------------ + +local UIEquipmentList = {} + +function UIEquipmentList.new( px, py, x, y, w, h ) + local self = UIElement.new( px, py, x, y, w, h ):addInstance( 'UIEquipmentList' ) + + -- ------------------------------------------------ + -- Private Attributes + -- ------------------------------------------------ + + local character + local equipment + local list + + -- ------------------------------------------------ + -- Private Methods + -- ------------------------------------------------ + + --- + -- Iterates over all equipment slots an UIEquipmentSlot for them and stores + -- them based on their sort order. + -- @treturn table A sequence containing the UIEquipmentSlots. + -- + local function populateItemList() + local nList = {} + for _, slot in pairs( equipment:getSlots() ) do + local uiItem = UIEquipmentSlot.new( self.ax, self.ay, 0, slot:getSortOrder(), self.w, 1 ) + uiItem:init( slot ) + nList[slot:getSortOrder()] = uiItem + end + return nList + end + + -- ------------------------------------------------ + -- Public Methods + -- ------------------------------------------------ + + --- + -- Recreates the item list. + -- + function self:refresh() + list = populateItemList() + end + + --- + -- Initialises the UIEquipmentList. + -- @tparam Character character The character to create the equipment list for. + -- + function self:init( ncharacter ) + character = ncharacter + equipment = character:getEquipment() + self:refresh() + end + + --- + -- Draws the list. + -- + function self:draw() + for _, slot in ipairs( list ) do + slot:draw() + end + end + + --- + -- Drags an item below the mouse cursor. + -- @treturn UIEquipmentSlot The UIEquipmentSlot containing the actual item. + -- + function self:drag() + for _, uiItem in ipairs( list ) do + if uiItem:isMouseOver() and uiItem:getSlot():containsItem() and not uiItem:getSlot():getItem():isPermanent() then + local item = equipment:removeItem( uiItem:getSlot() ) + + if item:instanceOf( 'Container' ) then + character:getInventory():dropItems( character:getTile() ) + end + + self:refresh() + return item, uiItem:getSlot() + end + end + end + + --- + -- Drops an item onto this list. If the slot the item belongs to already + -- contains an item, that item will be swapped to the inventory the new item + -- is coming from. + -- @tparam Item item The new item to place in an equipment slot. + -- @tparam UIInventoryList origin The inventory list the item is coming from. + -- @treturn boolean Wether or not the drop action was succesful. + -- + function self:drop( item, origin ) + -- Stacks and unequippable items can't be dropped on equipment lists. + if item:instanceOf( 'ItemStack' ) or not item:isEquippable() then + return false + end + + local success = false + for _, uiItem in ipairs( list ) do + local slot = uiItem:getSlot() + if uiItem:isMouseOver() and item:isSameType( slot:getItemType(), slot:getSubType() ) then + if slot:containsItem() then + local tmp = equipment:removeItem( slot ) + success = equipment:addItem( slot, item ) + origin:drop( tmp ) + else + success = equipment:addItem( slot, item ) + end + end + end + + self:refresh() + return success + end + + function self:getSlotBelowCursor() + for _, uiItem in ipairs( list ) do + if uiItem:isMouseOver() then + return uiItem:getSlot() + end + end + end + + function self:getItemBelowCursor() + for _, uiItem in ipairs( list ) do + if uiItem:isMouseOver() then + return uiItem:getSlot():getItem() + end + end + end + + function self:highlight( nitem ) + for _, uiItem in ipairs( list ) do + uiItem:highlight( nitem ) + end + end + + function self:doesFit( item ) + local slot = self:getSlotBelowCursor() + if not slot then + return false + end + return item:isSameType( slot:getItemType(), slot:getSubType() ) + end + + return self +end + +return UIEquipmentList diff --git a/src/ui/elements/inventory/UIEquipmentSlot.lua b/src/ui/elements/inventory/UIEquipmentSlot.lua new file mode 100644 index 00000000..9f466077 --- /dev/null +++ b/src/ui/elements/inventory/UIEquipmentSlot.lua @@ -0,0 +1,102 @@ +--- +-- @module UIEquipmentSlot +-- + +-- ------------------------------------------------ +-- Required Modules +-- ------------------------------------------------ + +local UIElement = require( 'src.ui.elements.UIElement' ) +local UIBackground = require( 'src.ui.elements.UIBackground' ) +local UILabel = require( 'src.ui.elements.UILabel' ) + +-- ------------------------------------------------ +-- Module +-- ------------------------------------------------ + +local UIEquipmentSlot = {} + +-- ------------------------------------------------ +-- Constructor +-- ------------------------------------------------ + +--- +-- @tparam EquipmentSlot slot The equipment slot to use. +-- @tparam number x The x-offset at which to draw this ui-element. +-- @tparam number y The y-offset at which to draw this ui-element. +-- @tparam number w The width of this ui-element. +-- @tparam number h The height of this ui-element. +-- +function UIEquipmentSlot.new( px, py, x, y, w, h ) + local self = UIElement.new( px, py, x, y, w, h ):addInstance( 'UIEquipmentSlot' ) + + -- ------------------------------------------------ + -- Private Attributes + -- ------------------------------------------------ + + local background + local label + local slot + local highlight + + -- ------------------------------------------------ + -- Private Methods + -- ------------------------------------------------ + + local function updateBackground() + if highlight then + background:setColor( 'ui_equipment_highlight' ) + elseif self:isMouseOver() then + background:setColor( 'ui_equipment_mouseover' ) + else + background:setColor( 'sys_background' ) + end + end + + local function updateLabel() + if slot:containsItem() then + label:setText( slot:getItem():getID() ) + label:setColor( 'ui_equipment_item' ) + else + label:setText( slot:getID() ) + label:setColor( 'ui_equipment_empty' ) + label:setUpper( true ) + end + end + + -- ------------------------------------------------ + -- Public Methods + -- ------------------------------------------------ + + function self:init( nslot ) + slot = nslot + + background = UIBackground.new( self.ax, self.ay, 0, 0, self.w, self.h ) + + label = UILabel.new( self.ax, self.ay, 0, 0, self.w, self.h ) + end + + function self:draw() + updateBackground() + background:draw() + + updateLabel() + label:draw() + end + + function self:getSlot() + return slot + end + + function self:highlight( nitem ) + if not nitem then + highlight = false + return + end + highlight = nitem:isSameType( slot:getItemType(), slot:getSubType() ) + end + + return self +end + +return UIEquipmentSlot diff --git a/src/ui/elements/inventory/UIInventoryDragboard.lua b/src/ui/elements/inventory/UIInventoryDragboard.lua new file mode 100644 index 00000000..50d2c585 --- /dev/null +++ b/src/ui/elements/inventory/UIInventoryDragboard.lua @@ -0,0 +1,134 @@ +--- +-- @module UIInventoryDragboard +-- + +-- ------------------------------------------------ +-- Required Modules +-- ------------------------------------------------ + +local Observable = require( 'src.util.Observable' ) +local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) +local Translator = require( 'src.util.Translator' ) +local GridHelper = require( 'src.util.GridHelper' ) +local UIBackground = require( 'src.ui.elements.UIBackground' ) + +-- ------------------------------------------------ +-- Module +-- ------------------------------------------------ + +local UIInventoryDragboard = {} + +-- ------------------------------------------------ +-- Constants +-- ------------------------------------------------ + +local ITEM_WIDTH = 20 + +-- ------------------------------------------------ +-- Constructor +-- ------------------------------------------------ + +function UIInventoryDragboard.new() + local self = Observable.new():addInstance( 'UIInventoryDragboard' ) + + -- ------------------------------------------------ + -- Private Attributes + -- ------------------------------------------------ + + local background = UIBackground.new( 0, 0, 0, 0, ITEM_WIDTH, 1 ) + local dragContext + + -- ------------------------------------------------ + -- Private Methods + -- ------------------------------------------------ + + local function returnItemToOrigin( item, origin ) + if origin:instanceOf( 'EquipmentSlot' ) then + origin:addItem( item ) + else + origin:drop( item ); + end + end + + local function createLabel() + local item = dragContext.item + local label = Translator.getText( item:getID() ) + if item:instanceOf( 'ItemStack' ) and item:getItemCount() > 1 then + label = string.format( '%s (%d)', label, item:getItemCount() ) + end + return label + end + + -- ------------------------------------------------ + -- Public Methods + -- ------------------------------------------------ + + function self:draw( lists ) + if not dragContext then + return + end + + local tw, th = TexturePacks.getTileDimensions() + local gx, gy = GridHelper.pixelsToGrid( love.mouse.getPosition() ) + + -- Move background and draw it. + background:setOrigin( gx, gy ) + background:setColor( 'ui_inventory_drag_bg' ) + background:draw() + + -- Check if the dragged item would fit. + TexturePacks.setColor( 'ui_inventory_drag_text' ) + for _, list in pairs( lists ) do + if list:isMouseOver() then + if not list:doesFit( dragContext.item ) then + TexturePacks.setColor( 'ui_inventory_full' ) + break + end + end + end + + love.graphics.print( createLabel(), (gx+1) * tw, gy * th ) + TexturePacks.resetColor() + end + + function self:drag( item, origin ) + assert( item and origin, 'Missing parameters.' ) + love.mouse.setVisible( false ) + dragContext = { item = item, origin = origin } + end + + function self:drop( target ) + love.mouse.setVisible( true ) + + if not target then + returnItemToOrigin( dragContext.item, dragContext.origin ) + else + local success = target:drop( dragContext.item, dragContext.origin ) + if not success then + returnItemToOrigin( dragContext.item, dragContext.origin ) + end + end + + self:clearDragContext() + end + + function self:hasDragContext() + return dragContext ~= nil + end + + function self:getDragContext() + return dragContext + end + + function self:clearDragContext() + dragContext = nil + end + + function self:getDraggedItem() + return dragContext.item + end + + return self +end + +return UIInventoryDragboard diff --git a/src/ui/elements/inventory/UIInventoryItem.lua b/src/ui/elements/inventory/UIInventoryItem.lua new file mode 100644 index 00000000..96f49d80 --- /dev/null +++ b/src/ui/elements/inventory/UIInventoryItem.lua @@ -0,0 +1,94 @@ +--- +-- @module UIInventoryItem +-- + +-- ------------------------------------------------ +-- Required Modules +-- ------------------------------------------------ + +local UIElement = require( 'src.ui.elements.UIElement' ) +local UIBackground = require( 'src.ui.elements.UIBackground' ) +local UITranslatedLabel = require( 'src.ui.elements.UITranslatedLabel' ) +local UILabel = require( 'src.ui.elements.UILabel' ) + +-- ------------------------------------------------ +-- Module +-- ------------------------------------------------ + +local UIInventoryItem = {} + +function UIInventoryItem.new( item, px, py, x, y, w, h ) + local self = UIElement.new( px, py, x, y, w, h ):addInstance( 'UIInventoryItem' ) + + -- ------------------------------------------------ + -- Private Attributes + -- ------------------------------------------------ + + local background + local label + local amount + + -- ------------------------------------------------ + -- Private Methods + -- ------------------------------------------------ + + local function updateBackground() + if self:isMouseOver() then + background:setColor( 'ui_equipment_mouseover' ) + else + background:setColor( 'sys_background' ) + end + end + + local function createInfo() + local count = 1 + if item:instanceOf( 'ItemStack' ) and item:getItemCount() > 1 then + count = item:getItemCount() + end + amount = UILabel.new( self.ax, self.ay, self.w-2, 0, self.w, 1, count, 'ui_equipment_item' ) + end + + -- ------------------------------------------------ + -- Public Methods + -- ------------------------------------------------ + + function self:init() + background = UIBackground.new( self.ax, self.ay, 0, 0, self.w, self.h ) + label = UITranslatedLabel.new( self.ax, self.ay, 0, 0, self.w, 1, item:getID(), 'ui_equipment_item' ) + + createInfo() + end + + function self:draw() + updateBackground() + background:draw() + + label:draw() + amount:draw() + end + + function self:drag( rmb, fullstack ) + if item:instanceOf( 'ItemStack' ) and rmb then + if item:getItemCount() == 1 then + return item + else + return item:split() + end + elseif item:instanceOf( 'ItemStack' ) and not fullstack then + return item:getItem() + end + return item + end + + function self:getItem() + return item + end + + function self:hasItem() + return item ~= nil + end + + return self +end + +return UIInventoryItem diff --git a/src/ui/elements/inventory/UIInventoryList.lua b/src/ui/elements/inventory/UIInventoryList.lua new file mode 100644 index 00000000..363a5b34 --- /dev/null +++ b/src/ui/elements/inventory/UIInventoryList.lua @@ -0,0 +1,118 @@ +--- +-- @module UIInventoryList +-- + +-- ------------------------------------------------ +-- Required Modules +-- ------------------------------------------------ + +local UIElement = require( 'src.ui.elements.UIElement' ) +local UIInventoryItem = require( 'src.ui.elements.inventory.UIInventoryItem' ) +local UILabel = require( 'src.ui.elements.UILabel' ) + +-- ------------------------------------------------ +-- Module +-- ------------------------------------------------ + +local UIInventoryList = {} + +function UIInventoryList.new( px, py, x, y, w, h ) + local self = UIElement.new( px, py, x, y, w, h ):addInstance( 'UIInventoryList' ) + + -- ------------------------------------------------ + -- Private Attributes + -- ------------------------------------------------ + + local list + local inventory + local info + + -- ------------------------------------------------ + -- Private Methods + -- ------------------------------------------------ + + local function populateItemList() + local nList = {} + for offset, item in ipairs( inventory:getItems() ) do + -- Spawn elements at the list's position but offset them vertically. + local uiItem = UIInventoryItem.new( item, self.ax, self.ay, 0, offset, self.w, 1 ) + uiItem:init() + nList[#nList + 1] = uiItem + end + return nList + end + + local function generateStorageInfo() + local infoText = string.format( 'W: %0.1f/%0.1f V: %0.1f/%0.1f', inventory:getWeight(), inventory:getWeightLimit(), inventory:getVolume(), inventory:getVolumeLimit() ) + info = UILabel.new( self.ax, self.ay, 0, 0, self.w, 1, infoText, 'ui_text_dim' ) + return info + end + + -- ------------------------------------------------ + -- Public Methods + -- ------------------------------------------------ + + function self:refresh() + list = populateItemList() + info = generateStorageInfo() + end + + function self:init( ninventory ) + inventory = ninventory + self:refresh() + end + + function self:draw() + for _, item in ipairs( list ) do + item:draw() + end + + info:draw() + end + + function self:drag( rmb, fullstack ) + for _, uiItem in ipairs( list ) do + if uiItem:isMouseOver() then + local item = uiItem:drag( rmb, fullstack ) + inventory:removeItem( item ) + self:refresh() + return item + end + end + end + + function self:drop( item ) + for _, uiItem in ipairs( list ) do + if uiItem:isMouseOver() then + local success = inventory:insertItem( item, uiItem:getItem() ) + if success then + self:refresh() + return true + end + end + end + + local success = inventory:addItem( item ) + if success then + self:refresh() + return true + end + return false + end + + function self:getItemBelowCursor() + for _, uiItem in ipairs( list ) do + if uiItem:isMouseOver() then + return uiItem:getItem() + end + end + end + + function self:doesFit( item ) + return inventory:doesFit( item:getWeight(), item:getVolume() ) + end + + return self +end + +return UIInventoryList diff --git a/src/ui/elements/inventory/UIItemStats.lua b/src/ui/elements/inventory/UIItemStats.lua new file mode 100644 index 00000000..2d304af4 --- /dev/null +++ b/src/ui/elements/inventory/UIItemStats.lua @@ -0,0 +1,206 @@ +--- +-- @module UIItemStats +-- + +-- ------------------------------------------------ +-- Required Modules +-- ------------------------------------------------ + +local UIElement = require( 'src.ui.elements.UIElement' ) +local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) +local Translator = require( 'src.util.Translator' ) + +-- ------------------------------------------------ +-- Module +-- ------------------------------------------------ + +local UIItemStats = {} +local UIScrollArea = require( 'src.ui.elements.UIScrollArea' ) + +-- ------------------------------------------------ +-- Constants +-- ------------------------------------------------ + +local ITEM_TYPES = require('src.constants.ITEM_TYPES') +local WEAPON_TYPES = require( 'src.constants.WEAPON_TYPES' ) + +local COLUMN_1 = 0 +local COLUMN_2 = 10 +local COLUMN_3 = 22 +local COLUMN_4 = 30 + +local WEAPON_COLUMN_MODE = 0 +local WEAPON_COLUMN_AP = 20 +local WEAPON_COLUMN_ACC = 25 +local WEAPON_COLUMN_ATTACKS = 30 + +local VERTICAL_DESCRIPTION_OFFSET = 12 + +-- ------------------------------------------------ +-- Constructor +-- ------------------------------------------------ + +function UIItemStats.new( px, py, x, y, w, h ) + local self = UIElement.new( px, py, x, y, w, h ):addInstance( 'UIItemStats' ) + + -- ------------------------------------------------ + -- Private Attributes + -- ------------------------------------------------ + + local nameColor = TexturePacks.getColor( 'ui_inventory_stats_name' ) + local typeColor = TexturePacks.getColor( 'ui_inventory_stats_type' ) + local valueColor = TexturePacks.getColor( 'ui_inventory_stats_value' ) + local descColor = TexturePacks.getColor( 'ui_inventory_description' ) + + local stats + local description + + -- ------------------------------------------------ + -- Private Methods + -- ------------------------------------------------ + + local function addHeader( ntext, item ) + ntext:add({ nameColor, Translator.getText( item:getID() )}, 0, 0 ) + end + + local function addTypeInformation( ntext, item, tw, th ) + ntext:add({ typeColor, 'Type:' }, COLUMN_1 * tw, 2 * th ) + ntext:add({ valueColor, item:getItemType() }, COLUMN_2 * tw, 2 * th ) + + -- Add subtype (if the item has one). + if item:getItemType() == ITEM_TYPES.WEAPON then + ntext:add({ typeColor, 'Weapon:' }, COLUMN_3 * tw, 2 * th ) + ntext:add({ valueColor, item:getSubType() }, COLUMN_4 * tw, 2 * th ) + end + end + + local function addWeightInformation( ntext, item, tw, th ) + local wgt = string.format( '%.1f', item:getWeight() ) + ntext:add({ typeColor, 'WGT:' }, COLUMN_1 * tw, 3 * th ) + ntext:add({ valueColor, wgt }, COLUMN_2 * tw, 3 * th ) + + local vol = string.format( '%.1f', item:getVolume() ) + ntext:add({ typeColor, 'VOL:' }, COLUMN_3 * tw, 3 * th ) + ntext:add({ valueColor, vol }, COLUMN_4 * tw, 3 * th ) + end + + local function addContainerInformation( ntext, item, tw, th ) + if item:getItemType() == ITEM_TYPES.CONTAINER then + local volumeLimit = item:getCarryCapacity() + ntext:add({ typeColor, 'Capacity:' }, COLUMN_1 * tw, 4 * th ) + ntext:add({ valueColor, volumeLimit }, COLUMN_2 * tw, 4 * th ) + end + end + + local function addWeaponInformation( ntext, item, tw, th ) + if item:getItemType() ~= ITEM_TYPES.WEAPON then + return + end + + if item:getSubType() == WEAPON_TYPES.RANGED then + local ammo = item:getMagazine():getCaliber() + ntext:add({ typeColor, 'Ammo:' }, COLUMN_1 * tw, 4 * th ) + ntext:add({ valueColor, ammo }, COLUMN_2 * tw, 4 * th ) + love.graphics.print( 'Ammo: ' .. item:getMagazine():getCaliber(), (x + w * 0.5) * tw, (y + 2) * th ) + end + + ntext:add({ typeColor, 'MODE' }, WEAPON_COLUMN_MODE * tw, 6 * th ) + ntext:add({ typeColor, 'AP' }, WEAPON_COLUMN_AP * tw, 6 * th ) + ntext:add({ typeColor, 'ACC' }, WEAPON_COLUMN_ACC * tw, 6 * th ) + ntext:add({ typeColor, 'ATTACKS' }, WEAPON_COLUMN_ATTACKS * tw, 6 * th ) + for i, mode in ipairs( item:getModes() ) do + ntext:add({ valueColor, mode.name }, WEAPON_COLUMN_MODE * tw, (6+i) * th ) + ntext:add({ valueColor, mode.cost }, WEAPON_COLUMN_AP * tw, (6+i) * th ) + ntext:add({ valueColor, mode.accuracy }, WEAPON_COLUMN_ACC * tw, (6+i) * th ) + ntext:add({ valueColor, mode.attacks }, WEAPON_COLUMN_ATTACKS * tw, (6+i) * th ) + end + end + + local function addDescriptionHeader( ntext, tw, th ) + ntext:add({ typeColor, 'Description:' }, COLUMN_1 * tw, (VERTICAL_DESCRIPTION_OFFSET-1) * th ) + end + + local function assembleText( item ) + local ntext = love.graphics.newText( TexturePacks.getFont():get() ) + local tw, th = TexturePacks.getGlyphDimensions() + + -- Header. + addHeader( ntext, item, tw, th ) + + -- Add type. + addTypeInformation( ntext, item, tw, th ) + + -- Add weight and volume information. + addWeightInformation( ntext, item, tw, th ) + + -- Add container information. + addContainerInformation( ntext, item, tw, th ) + + -- Add weapon information. + addWeaponInformation( ntext, item, tw, th ) + + -- Add description header. + addDescriptionHeader( ntext, tw, th ) + + return ntext + end + + local function addDescriptionArea( item ) + description = UIScrollArea.new( self.ax, self.ay, 0, VERTICAL_DESCRIPTION_OFFSET, self.w, self.h-VERTICAL_DESCRIPTION_OFFSET ) + description:init({ descColor, Translator.getText( item:getDescriptionID() )}) + end + + -- ------------------------------------------------ + -- Public Methods + -- ------------------------------------------------ + + function self:draw() + if not stats then + return + end + + local tw, th = TexturePacks.getTileDimensions() + + love.graphics.draw( stats, self.ax * tw, self.ay * th ) + description:draw() + end + + function self:setItem( item ) + stats = assembleText( item ) + addDescriptionArea( item ) + end + + function self:keypressed( k ) + if not self:isMouseOver() or not description then + return + end + + if k == 'up' then + description:scroll( -1 ) + elseif k == 'down' then + description:scroll( 1 ) + end + end + + function self:mousepressed( mx, my ) + if not self:isMouseOver() or not description then + return + end + + if description:isMouseOver() then + description:mousepressed( mx, my ) + end + end + + function self:wheelmoved( _, dy ) + if not self:isMouseOver() or not description then + return + end + + description:scroll( dy ) + end + + return self +end + +return UIItemStats diff --git a/src/ui/inventory/ItemStats.lua b/src/ui/inventory/ItemStats.lua deleted file mode 100644 index 969c60c0..00000000 --- a/src/ui/inventory/ItemStats.lua +++ /dev/null @@ -1,70 +0,0 @@ -local Object = require( 'src.Object' ); -local Translator = require( 'src.util.Translator' ); -local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) - -local ItemStats = {}; - -local ITEM_TYPES = require('src.constants.ITEM_TYPES') -local WEAPON_TYPES = require( 'src.constants.WEAPON_TYPES' ) - -function ItemStats.new( x, y, w, h ) - local self = Object.new():addInstance( 'ItemStats' ); - - local item; - local tw, th = TexturePacks.getTileDimensions() - - local function drawContainerStats() - local volumeLimit = item:getCarryCapacity(); - love.graphics.print( 'Volume Limit: ' .. volumeLimit, x * tw, (y + 4) * th ) - end - - local function drawWeaponStats() - local weaponType = item:getSubType(); - love.graphics.print( 'Weapon Type: ' .. weaponType, x * tw, (y + 2) * th ) - if weaponType == WEAPON_TYPES.RANGED then - love.graphics.print( 'Ammo: ' .. item:getMagazine():getCaliber(), (x + w * 0.5) * tw, (y + 2) * th ) - end - - for i = 1, #item:getModes() do - local mode = item:getModes()[i]; - love.graphics.print( mode.name, x * tw, ( y + 3 + i ) * th ) - love.graphics.print( 'AP ' .. mode.cost, ( x+10 ) * tw, ( y + 3 + i ) * th ) - if not ( weaponType == WEAPON_TYPES.THROWN ) then - love.graphics.print( 'ACC ' .. mode.accuracy, ( x+15 ) * tw, ( y + 3 + i ) * th ) - love.graphics.print( 'ATT ' .. mode.attacks, ( x+20 ) * tw, ( y + 3 + i ) * th ) - end - end - end - - function self:draw() - love.graphics.setScissor( x * tw, y * th, w * tw, h * th ) - - if item then - love.graphics.print( 'Name: ' .. Translator.getText( item:getID() ), x * tw, y * th ) - love.graphics.print( 'Type: ' .. item:getItemType(), x * tw, (y + 1) * th ) - love.graphics.print( 'WGT: ' .. string.format( '%.1f', item:getWeight() ), (x + w * 0.5) * tw, (y + 1) * th ) - love.graphics.print( 'VOL: ' .. string.format( '%.1f', item:getVolume() ), (x + 5 + w * 0.5) * tw, (y + 1) * th ) - - if item:getItemType() == ITEM_TYPES.CONTAINER then - drawContainerStats(); - elseif item:getItemType() == ITEM_TYPES.WEAPON then - drawWeaponStats(); - end - end - - love.graphics.setScissor(); - end - - function self:setItem( nitem ) - if nitem:instanceOf( 'ItemStack' ) then - item = nitem:getItem(); - return; - end - - item = nitem; - end - - return self; -end - -return ItemStats; diff --git a/src/ui/inventory/ScrollArea.lua b/src/ui/inventory/ScrollArea.lua deleted file mode 100644 index 2150ad18..00000000 --- a/src/ui/inventory/ScrollArea.lua +++ /dev/null @@ -1,70 +0,0 @@ -local Object = require( 'src.Object' ); -local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) - -local ScrollArea = {}; - -function ScrollArea.new( x, y, w, h ) - local self = Object.new():addInstance( 'ScrollArea' ); - - local text; - local _, lines; - local verticalOffset = 0; - local tileset = TexturePacks.getTileset() - local font = TexturePacks.getFont() - local tw, th = tileset:getTileDimensions() - - function self:setText( ntext ) - text = ntext; - _, lines = font:get():getWrap( text, w * tw - tw ) - end - - local function drawScrollBar() - if not lines or #lines < h then - return; - end - - if verticalOffset < 0 then - love.graphics.draw( tileset:getSpritesheet(), tileset:getSprite( 'ui_scroll_area_up' ), (x + w - 2) * tw, ( y - 2 ) * th ) - end - - if verticalOffset > h - #lines then - love.graphics.draw( tileset:getSpritesheet(), tileset:getSprite( 'ui_scroll_area_down' ), (x + w - 1) * tw, ( y - 2 ) * th ) - end - end - - function self:draw() - love.graphics.setScissor( x * tw, y * th, w * tw, h * th ) - for i = 1, #lines do - love.graphics.print( lines[i], x * tw, ( y + i - 1 + verticalOffset ) * th ) - end - - love.graphics.setScissor(); - - drawScrollBar(); - end - - function self:scrollVertically( dir ) - if not lines or #lines < h then - return; - end - - if dir == 1 and verticalOffset == 0 then - return; - end - - if dir == -1 and verticalOffset == h - #lines then - return; - end - - verticalOffset = verticalOffset + dir; - end - - function self:setDimensions( nx, ny, nw, nh ) - x, y, w, h = nx, ny, nw, nh; - _, lines = font:get():getWrap( text, w * tw - tw ) - end - - return self; -end - -return ScrollArea; diff --git a/src/ui/inventory/UIEquipmentItem.lua b/src/ui/inventory/UIEquipmentItem.lua deleted file mode 100644 index 1606ef96..00000000 --- a/src/ui/inventory/UIEquipmentItem.lua +++ /dev/null @@ -1,65 +0,0 @@ -local Object = require( 'src.Object' ); -local Translator = require( 'src.util.Translator' ); -local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) - -local UIEquipmentItem = {}; - ---- --- This class actually holds an EquipmentSlot object instead of an item. --- -function UIEquipmentItem.new( id, x, y, width, height, slot ) - local self = Object.new():addInstance( 'UIEquipmentItem' ); - - local mouseOver = false; - local highlight = false; - - local function createLabel() - if not slot:containsItem() then - local text = Translator.getText( id ); - return string.upper( text ); - else - return Translator.getText( slot:getItem():getID() ); - end - end - - function self:update() - local mx, my = love.mouse.getPosition(); - mouseOver = ( mx > x and mx < x + width and my > y and my < y + height ); - end - - function self:isMouseOver() - return mouseOver; - end - - function self:draw() - if highlight then - TexturePacks.setColor( 'ui_equipment_highlight' ) - elseif self:isMouseOver() then - TexturePacks.setColor( 'ui_equipment_mouseover' ) - else - TexturePacks.setColor( 'sys_background' ) - end - - love.graphics.rectangle( 'fill', x, y, width, height ); - - if slot:containsItem() then - TexturePacks.setColor( 'ui_equipment_item' ) - else - TexturePacks.setColor( 'ui_equipment_empty' ) - end - - love.graphics.print( createLabel(), x, y ); - end - - function self:highlight( nitem ) - highlight = nitem and nitem:isSameType( slot:getItemType(), slot:getSubType() ) or false - end - - function self:getSlot() - return slot; - end - - return self; -end - -return UIEquipmentItem; diff --git a/src/ui/inventory/UIEquipmentList.lua b/src/ui/inventory/UIEquipmentList.lua deleted file mode 100644 index 121d075d..00000000 --- a/src/ui/inventory/UIEquipmentList.lua +++ /dev/null @@ -1,141 +0,0 @@ -local Object = require( 'src.Object' ); -local UIEquipmentItem = require( 'src.ui.inventory.UIEquipmentItem' ); -local Translator = require( 'src.util.Translator' ); -local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) - --- ------------------------------------------------ --- Module --- ------------------------------------------------ - -local UIEquipmentList = {}; - --- ------------------------------------------------ --- Constructor --- ------------------------------------------------ - -function UIEquipmentList.new( x, y, width, id, character ) - local self = Object.new():addInstance( 'UIEquipmentList' ); - - -- ------------------------------------------------ - -- Private Attributes - -- ------------------------------------------------ - - local equipment = character:getEquipment(); - local list; - local tw, th = TexturePacks.getTileDimensions() - - -- ------------------------------------------------ - -- Private Methods - -- ------------------------------------------------ - - local function regenerate() - list = {}; - - for _, slot in pairs( equipment:getSlots() ) do - local uiItem = UIEquipmentItem.new( slot:getID(), x, y + slot:getSortOrder() * tw, width, th, slot ) - list[slot:getSortOrder()] = uiItem; - end - end - - -- ------------------------------------------------ - -- Public Methods - -- ------------------------------------------------ - - function self:init() - regenerate(); - end - - function self:draw() - for _, slot in ipairs( list ) do - slot:draw(); - end - end - - function self:update( dt ) - for _, slot in ipairs( list ) do - slot:update( dt ); - end - end - - function self:isMouseOver() - local mx = love.mouse.getX(); - return ( mx > x and mx < x + width ); - end - - --- - -- Drops an item onto this list. If the slot the item belongs to already - -- contains an item, that item will be swapped to the inventory the new item - -- is coming from. - -- @param item (Item) The new item to place in a equipment slot. - -- @param origin (UIInventoryList) The inventory list the item is coming from. - -- - function self:drop( item, origin ) - local success = false; - if item:instanceOf( 'ItemStack' ) or not item:isEquippable() then - return success; - end - - for _, uiItem in ipairs( list ) do - local slot = uiItem:getSlot(); - if uiItem:isMouseOver() and item:isSameType( slot:getItemType(), slot:getSubType() ) then - if slot:containsItem() then - local tmp = equipment:removeItem( slot ); - success = equipment:addItem( slot, item ); - origin:drop( tmp ); - else - success = equipment:addItem( slot, item ); - end - end - end - - regenerate(); - return success; - end - - --- - -- Checks if the mouse is over one of the UIItems in this list. If it is, - -- the contained Item will be removed and returned. - -- @return (Item) The item contained in the UIItem. - -- - function self:drag() - for _, uiItem in ipairs( list ) do - if uiItem:isMouseOver() and uiItem:getSlot():containsItem() and not uiItem:getSlot():getItem():isPermanent() then - local item = equipment:removeItem( uiItem:getSlot() ); - - -- TODO warn player - if item:instanceOf( 'Container' ) then - character:getInventory():dropItems( character:getTile() ); - end - - regenerate(); - return item, uiItem:getSlot(); - end - end - end - - function self:highlightSlot( nitem ) - for _, uiItem in ipairs( list ) do - uiItem:highlight( nitem ); - end - end - - function self:getItemBelowCursor() - for _, uiItem in ipairs( list ) do - if uiItem:isMouseOver() then - return uiItem:getSlot():getItem(); - end - end - end - - function self:getLabel() - return Translator.getText( id ); - end - - function self:doesFit() - return true; - end - - return self; -end - -return UIEquipmentList; diff --git a/src/ui/inventory/UIInventoryItem.lua b/src/ui/inventory/UIInventoryItem.lua deleted file mode 100644 index 059015f0..00000000 --- a/src/ui/inventory/UIInventoryItem.lua +++ /dev/null @@ -1,70 +0,0 @@ -local Object = require( 'src.Object' ); -local Translator = require( 'src.util.Translator' ); -local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) - -local UIInventoryItem = {}; - -function UIInventoryItem.new( x, y, width, height, item ) - local self = Object.new():addInstance( 'UIInventoryItem' ); - - local mouseOver = false; - - local function createLabel() - if not item then - return Translator.getText( 'inventory_empty_slot' ); - else - local text = Translator.getText( item:getID() ) - if item:instanceOf( 'ItemStack' ) and item:getItemCount() > 1 then - text = string.format( '%s (%d)', text, item:getItemCount() ); - end - return text; - end - end - - function self:draw() - if mouseOver then - TexturePacks.setColor( 'ui_equipment_mouseover' ) - else - TexturePacks.setColor( 'sys_background' ) - end - love.graphics.rectangle( 'fill', x, y, width, height ); - - TexturePacks.setColor( 'ui_equipment_item' ) - love.graphics.print( createLabel(), x, y ); - TexturePacks.resetColor() - end - - function self:update() - local mx, my = love.mouse.getPosition(); - mouseOver = ( mx > x and mx < x + width and my > y and my < y + height ); - end - - function self:isMouseOver() - return mouseOver; - end - - function self:getItem() - return item; - end - - function self:hasItem() - return item ~= nil; - end - - function self:drag( rmb, fullstack ) - if item:instanceOf( 'ItemStack' ) and rmb then - if item:getItemCount() == 1 then - return item; - else - return item:split(); - end - elseif item:instanceOf( 'ItemStack' ) and not fullstack then - return item:getItem(); - end - return item; - end - - return self; -end - -return UIInventoryItem; diff --git a/src/ui/inventory/UIInventoryList.lua b/src/ui/inventory/UIInventoryList.lua deleted file mode 100644 index a0fe559f..00000000 --- a/src/ui/inventory/UIInventoryList.lua +++ /dev/null @@ -1,115 +0,0 @@ -local Object = require( 'src.Object' ); -local UIInventoryItem = require( 'src.ui.inventory.UIInventoryItem' ); -local Translator = require( 'src.util.Translator' ); -local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) - --- ------------------------------------------------ --- Module --- ------------------------------------------------ - -local UIInventoryList = {}; - --- ------------------------------------------------ --- Constructor --- ------------------------------------------------ - -function UIInventoryList.new( x, y, width, id, inventory ) - local self = Object.new():addInstance( 'UIInventoryList' ); - - -- ------------------------------------------------ - -- Private Attributes - -- ------------------------------------------------ - - local list; - local tw, th = TexturePacks.getTileDimensions() - - -- ------------------------------------------------ - -- Private Methods - -- ------------------------------------------------ - - local function regenerate() - list = {}; - for i, item in ipairs( inventory:getItems() ) do - list[#list + 1] = UIInventoryItem.new( x, y + i * tw, width, th, item ) - end - end - - -- ------------------------------------------------ - -- Public Methods - -- ------------------------------------------------ - - function self:init() - regenerate(); - end - - function self:draw() - love.graphics.print( string.format( 'W: %0.1f/%0.1f V: %0.1f/%0.1f', inventory:getWeight(), inventory:getWeightLimit(), inventory:getVolume(), inventory:getVolumeLimit() ), x, y ); - for _, slot in ipairs( list ) do - slot:draw(); - end - end - - function self:update( dt ) - for _, slot in ipairs( list ) do - slot:update( dt ); - end - end - - function self:isMouseOver() - local mx = love.mouse.getX(); - for _, uiItem in ipairs( list ) do - uiItem:isMouseOver(); - end - return ( mx > x and mx < x + width ); - end - - function self:drop( item ) - for _, uiItem in ipairs( list ) do - if uiItem:isMouseOver() then - local success = inventory:insertItem( item, uiItem:getItem() ); - if success then - regenerate(); - return true; - end - end - end - - local success = inventory:addItem( item ); - if success then - regenerate(); - return true; - end - return false; - end - - function self:drag( rmb, fullstack ) - for _, uiItem in ipairs( list ) do - if uiItem:isMouseOver() then - local item = uiItem:drag( rmb, fullstack ); - inventory:removeItem( item ); - regenerate(); - return item; - end - end - end - - function self:getItemBelowCursor() - for _, uiItem in ipairs( list ) do - if uiItem:isMouseOver() then - return uiItem:getItem(); - end - end - end - - function self:getLabel() - return Translator.getText( id ); - end - - function self:doesFit( item ) - return inventory:doesFit( item:getWeight(), item:getVolume() ); - end - - return self; -end - -return UIInventoryList; diff --git a/src/ui/overlays/ConeOverlay.lua b/src/ui/overlays/ConeOverlay.lua index 8e2e673c..53ec34b7 100644 --- a/src/ui/overlays/ConeOverlay.lua +++ b/src/ui/overlays/ConeOverlay.lua @@ -130,7 +130,7 @@ function ConeOverlay.new( game, pulser ) -- 2 means the projectile might be blocked by a world object or character. -- 3 means the projectile will be blocked by a world object. local nstatus = 1 - if tile:isOccupied() then + if tile:isOccupied() or not character:canSee( tile ) then nstatus = 2 elseif tile:hasWorldObject() and height <= tile:getWorldObject():getHeight() then -- Indestructible worldobjects block the shot. diff --git a/src/ui/overlays/DebugGrid.lua b/src/ui/overlays/DebugGrid.lua new file mode 100644 index 00000000..4f0341dd --- /dev/null +++ b/src/ui/overlays/DebugGrid.lua @@ -0,0 +1,55 @@ +--- +-- Draws a grid overlay based on the tile size of the texturepack. +-- This can be used to check proper alignment of tiles. +-- @module DebugGrid +-- + +-- ------------------------------------------------ +-- Required Modules +-- ------------------------------------------------ + +local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) + +-- ------------------------------------------------ +-- Module +-- ------------------------------------------------ + +local DebugGrid = {} + +-- ------------------------------------------------ +-- Private Functions +-- ------------------------------------------------ + +local function mod( a, b ) + return a - math.floor( a / b ) * b +end + +-- ------------------------------------------------ +-- Public Functions +-- ------------------------------------------------ + +function DebugGrid.draw() + local sw, sh = love.graphics.getDimensions() + + local tileWidth, tileHeight = TexturePacks.getTileDimensions() + local glyphWidth, glyphHeight = TexturePacks.getGlyphDimensions() + + for x = 0, sw - 1 do + for y = 0, sh - 1 do + if mod( x, tileWidth ) == 0 and mod( y, tileHeight ) == 0 then + love.graphics.setColor( 100, 100, 100, 80 ) + love.graphics.rectangle( 'line', x, y, tileWidth, tileHeight ) + end + + if mod( x, glyphWidth ) == 0 and mod( y, glyphHeight ) == 0 then + love.graphics.setColor( 100, 100, 100, 60 ) + love.graphics.rectangle( 'line', x, y, glyphWidth, glyphHeight ) + end + end + end + + love.graphics.setColor( 255, 255, 255, 255 ) +end + + +return DebugGrid diff --git a/src/ui/screens/BaseScreen.lua b/src/ui/screens/BaseScreen.lua index c12b0c3a..4ccf0d0e 100644 --- a/src/ui/screens/BaseScreen.lua +++ b/src/ui/screens/BaseScreen.lua @@ -17,7 +17,6 @@ local CameraHandler = require( 'src.ui.CameraHandler' ) local MousePointer = require( 'src.ui.MousePointer' ) local CharacterSelector = require( 'src.ui.elements.CharacterSelector' ) local NextMissionSelector = require( 'src.ui.elements.NextMissionSelector' ) -local HealAllSelector = require( 'src.ui.elements.HealAllSelector' ) local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) -- ------------------------------------------------ @@ -39,7 +38,6 @@ function BaseScreen.new() local currentCharacter local characterSelector local nextMissionSelector - local healAllSelector local playerFaction function self:init( nplayerFaction, savegame ) @@ -56,14 +54,12 @@ function BaseScreen.new() nextMissionSelector:init() nextMissionSelector:observe( self ) - healAllSelector = HealAllSelector.new() - healAllSelector:init() - healAllSelector:observe( self ) - mapPainter = MapPainter.new() mapPainter:init( baseState:getMap(), baseState:getFactions() ) - camera = CameraHandler.new( baseState:getMap() ) + local mw, mh = baseState:getMap():getDimensions() + local tw, th = TexturePacks:getTileDimensions() + camera = CameraHandler.new( mw, mh, tw, th ) MousePointer.init( camera ) end @@ -75,7 +71,6 @@ function BaseScreen.new() characterSelector:draw() nextMissionSelector:draw() - healAllSelector:draw() end function self:update( dt ) @@ -88,7 +83,6 @@ function BaseScreen.new() mapPainter:update() characterSelector:update() nextMissionSelector:update() - healAllSelector:update() MousePointer.update() end @@ -104,6 +98,9 @@ function BaseScreen.new() ScreenManager.push( 'basemenu', baseState ) end if scancode == 'h' then + if not currentCharacter then + return + end ScreenManager.push( 'health', currentCharacter ) end @@ -113,22 +110,15 @@ function BaseScreen.new() function self:mousereleased() characterSelector:mousereleased() nextMissionSelector:mousereleased() - healAllSelector:mousereleased() end function self:mousemoved() characterSelector:mousemoved() nextMissionSelector:mousemoved() - healAllSelector:mousemoved() end function self:receive( event, ... ) - if event == 'HEAL_CHARACTERS' then - -- TODO Replace with proper healing system. - playerFaction:iterate( function( character ) - character:getBody():heal() - end) - elseif event == 'LOAD_COMBAT_MISSION' then + if event == 'LOAD_COMBAT_MISSION' then ScreenManager.pop() ScreenManager.push( 'combat', playerFaction ) elseif event == 'CHANGED_CHARACTER' then diff --git a/src/ui/screens/BootLoadingScreen.lua b/src/ui/screens/BootLoadingScreen.lua index 80eceef8..c851d679 100644 --- a/src/ui/screens/BootLoadingScreen.lua +++ b/src/ui/screens/BootLoadingScreen.lua @@ -7,67 +7,68 @@ -- Required Modules -- ------------------------------------------------ -local ScreenManager = require( 'lib.screenmanager.ScreenManager' ); -local Screen = require( 'lib.screenmanager.Screen' ); -local Log = require( 'src.util.Log' ); -local Translator = require( 'src.util.Translator' ); - +local ScreenManager = require( 'lib.screenmanager.ScreenManager' ) +local Screen = require( 'lib.screenmanager.Screen' ) +local Log = require( 'src.util.Log' ) +local Translator = require( 'src.util.Translator' ) local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) - -local ItemFactory = require( 'src.items.ItemFactory' ); -local TileFactory = require( 'src.map.tiles.TileFactory' ); -local BodyFactory = require( 'src.characters.body.BodyFactory' ); -local WorldObjectFactory = require( 'src.map.worldobjects.WorldObjectFactory' ); -local BehaviorTreeFactory = require( 'src.characters.ai.behaviortree.BehaviorTreeFactory' ); -local SoundManager = require( 'src.SoundManager' ); +local ItemFactory = require( 'src.items.ItemFactory' ) +local TileFactory = require( 'src.map.tiles.TileFactory' ) +local BodyFactory = require( 'src.characters.body.BodyFactory' ) +local WorldObjectFactory = require( 'src.map.worldobjects.WorldObjectFactory' ) +local BehaviorTreeFactory = require( 'src.characters.ai.behaviortree.BehaviorTreeFactory' ) +local SoundManager = require( 'src.SoundManager' ) local MapLoader = require( 'src.map.MapLoader' ) - +local MapGenerator = require( 'src.map.procedural.MapGenerator' ) +local PrefabLoader = require( 'src.map.procedural.PrefabLoader' ) local CharacterFactory = require( 'src.characters.CharacterFactory' ) -- ------------------------------------------------ -- Module -- ------------------------------------------------ -local BootLoadingScreen = {}; +local BootLoadingScreen = {} -- ------------------------------------------------ -- Constants -- ------------------------------------------------ -local DEFAULT_LOCALE = 'en_EN'; +local DEFAULT_LOCALE = 'en_EN' -- ------------------------------------------------ -- Constructor -- ------------------------------------------------ function BootLoadingScreen.new() - local self = Screen.new(); + local self = Screen.new() function self:init() - local startTime = love.timer.getTime(); + local startTime = love.timer.getTime() - Translator.init( DEFAULT_LOCALE ); + Translator.init( DEFAULT_LOCALE ) TexturePacks.load() - ItemFactory.loadTemplates(); - TileFactory.loadTemplates(); - BodyFactory.loadTemplates(); - WorldObjectFactory.loadTemplates(); - BehaviorTreeFactory.loadTemplates(); - SoundManager.loadResources(); + ItemFactory.loadTemplates() + TileFactory.loadTemplates() + BodyFactory.loadTemplates() + WorldObjectFactory.loadTemplates() + BehaviorTreeFactory.loadTemplates() + SoundManager.loadResources() CharacterFactory.init() MapLoader.load() + MapGenerator.load() + PrefabLoader.load() - local endTime = love.timer.getTime(); - Log.debug( string.format( 'Loading game resources took %.3f seconds!', endTime - startTime ), 'BootLoadingScreen' ); + local endTime = love.timer.getTime() + Log.debug( string.format( 'Loading game resources took %.3f seconds!', endTime - startTime ), 'BootLoadingScreen' ) - ScreenManager.switch( 'mainmenu' ); + ScreenManager.switch( 'mainmenu' ) end - return self; + return self end -return BootLoadingScreen; +return BootLoadingScreen diff --git a/src/ui/screens/CombatScreen.lua b/src/ui/screens/CombatScreen.lua index 1f82ba5e..e42b0bfd 100644 --- a/src/ui/screens/CombatScreen.lua +++ b/src/ui/screens/CombatScreen.lua @@ -43,7 +43,8 @@ function CombatScreen.new() userInterface = UserInterface.new( combatState ) - camera = CameraHandler.new( combatState:getMap() ) + local mw, mh = combatState:getMap():getDimensions() + camera = CameraHandler.new( mw, mh, tw, th ) overlayPainter = OverlayPainter.new( combatState ) diff --git a/src/ui/screens/GameOverScreen.lua b/src/ui/screens/GameOverScreen.lua index de087701..289528a0 100644 --- a/src/ui/screens/GameOverScreen.lua +++ b/src/ui/screens/GameOverScreen.lua @@ -1,44 +1,73 @@ -local ScreenManager = require( 'lib.screenmanager.ScreenManager' ); -local Screen = require( 'lib.screenmanager.Screen' ); -local Translator = require( 'src.util.Translator' ); -local Outlines = require( 'src.ui.elements.Outlines' ) +--- +-- @module GameOverScreen +-- + +-- ------------------------------------------------ +-- Required Modules +-- ------------------------------------------------ + +local ScreenManager = require( 'lib.screenmanager.ScreenManager' ) +local Screen = require( 'lib.screenmanager.Screen' ) +local Translator = require( 'src.util.Translator' ) local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) +local GridHelper = require( 'src.util.GridHelper' ) +local UIBackground = require( 'src.ui.elements.UIBackground' ) +local UIOutlines = require( 'src.ui.elements.UIOutlines' ) -- ------------------------------------------------ -- Module -- ------------------------------------------------ -local GameOverScreen = {}; +local GameOverScreen = {} -- ------------------------------------------------ -- Constants -- ------------------------------------------------ -local SCREEN_WIDTH = 30; -local SCREEN_HEIGHT = 16; +local UI_GRID_WIDTH = 30 +local UI_GRID_HEIGHT = 16 -- ------------------------------------------------ -- Constructor -- ------------------------------------------------ function GameOverScreen.new() - local self = Screen.new(); + local self = Screen.new() - local text; + -- ------------------------------------------------ + -- Private Attributes + -- ------------------------------------------------ + + local text local outlines - local px, py; - local tw, th = TexturePacks.getTileDimensions() + local background + local x, y local win local playerFaction - local function createOutlines( w, h ) - for x = 0, w - 1 do - for y = 0, h - 1 do - if x == 0 or x == (w - 1) or y == 0 or y == (h - 1) then - outlines:add( x, y ) - end - end + -- ------------------------------------------------ + -- Private Methods + -- ------------------------------------------------ + + --- + -- Generates the outlines for this screen. + -- + local function generateOutlines() + outlines = UIOutlines.new( x, y, 0, 0, UI_GRID_WIDTH, UI_GRID_HEIGHT ) + + -- Horizontal borders. + for ox = 0, UI_GRID_WIDTH-1 do + outlines:add( ox, 0 ) -- Top + outlines:add( ox, UI_GRID_HEIGHT-1 ) -- Bottom end + + -- Vertical outlines. + for oy = 0, UI_GRID_HEIGHT-1 do + outlines:add( 0, oy ) -- Left + outlines:add( UI_GRID_WIDTH-1, oy ) -- Right + end + + outlines:refresh() end -- ------------------------------------------------ @@ -49,24 +78,21 @@ function GameOverScreen.new() playerFaction = nplayerFaction win = nwin - px = math.floor( love.graphics.getWidth() / tw ) * 0.5 - math.floor( SCREEN_WIDTH * 0.5 ) - py = math.floor( love.graphics.getHeight() / th ) * 0.5 - math.floor( SCREEN_HEIGHT * 0.5 ) - px, py = px * tw, py * th + x, y = GridHelper.centerElement( UI_GRID_WIDTH, UI_GRID_HEIGHT ) - outlines = Outlines.new() - createOutlines( SCREEN_WIDTH, SCREEN_HEIGHT ) - outlines:refresh() + background = UIBackground.new( x, y, 0, 0, UI_GRID_WIDTH, UI_GRID_HEIGHT ) - text = win and Translator.getText( 'ui_win' ) or Translator.getText( 'ui_lose' ); + generateOutlines() + + text = win and Translator.getText( 'ui_win' ) or Translator.getText( 'ui_lose' ) end function self:draw() - TexturePacks.setColor( 'sys_background' ) - love.graphics.rectangle( 'fill', px, py, SCREEN_WIDTH * tw, SCREEN_HEIGHT * th ) - - outlines:draw( px, py ) + background:draw() + outlines:draw() - love.graphics.printf( text, px, py + 3 * tw, SCREEN_WIDTH * th, 'center' ) + local tw, th = TexturePacks.getTileDimensions() + love.graphics.printf( text, x * tw, (y+6) * th, UI_GRID_WIDTH * tw, 'center' ) end function self:keypressed() @@ -78,7 +104,7 @@ function GameOverScreen.new() end end - return self; + return self end -return GameOverScreen; +return GameOverScreen diff --git a/src/ui/screens/HealthScreen.lua b/src/ui/screens/HealthScreen.lua index f34cd1d4..3aff993b 100644 --- a/src/ui/screens/HealthScreen.lua +++ b/src/ui/screens/HealthScreen.lua @@ -1,9 +1,10 @@ -local Log = require( 'src.util.Log' ); local ScreenManager = require( 'lib.screenmanager.ScreenManager' ); local Screen = require( 'lib.screenmanager.Screen' ); local Translator = require( 'src.util.Translator' ); -local Outlines = require( 'src.ui.elements.Outlines' ) +local UIOutlines = require( 'src.ui.elements.UIOutlines' ) +local UIBackground = require( 'src.ui.elements.UIBackground' ) local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) +local GridHelper = require( 'src.util.GridHelper' ) -- ------------------------------------------------ -- Module @@ -15,8 +16,8 @@ local HealthScreen = {}; -- Constants -- ------------------------------------------------ -local SCREEN_WIDTH = 30; -local SCREEN_HEIGHT = 16; +local UI_GRID_WIDTH = 30 +local UI_GRID_HEIGHT = 16 -- ------------------------------------------------ -- Constructor @@ -28,22 +29,35 @@ function HealthScreen.new() local character; local characterType; + local background local outlines - local px, py; + local x, y local tw, th - local function createOutlines( w, h ) - for x = 0, w - 1 do - for y = 0, h - 1 do - -- Draw screen borders. - if x == 0 or x == (w - 1) or y == 0 or y == (h - 1) then - outlines:add( x, y ) - end - if y == 2 then - outlines:add( x, y ) - end - end + -- ------------------------------------------------ + -- Private Methods + -- ------------------------------------------------ + + --- + -- Generates the outlines for this screen. + -- + local function generateOutlines() + outlines = UIOutlines.new( x, y, 0, 0, UI_GRID_WIDTH, UI_GRID_HEIGHT ) + + -- Horizontal borders. + for ox = 0, UI_GRID_WIDTH-1 do + outlines:add( ox, 0 ) -- Top + outlines:add( ox, 2 ) -- Header + outlines:add( ox, UI_GRID_HEIGHT-1 ) -- Bottom + end + + -- Vertical outlines. + for oy = 0, UI_GRID_HEIGHT-1 do + outlines:add( 0, oy ) -- Left + outlines:add( UI_GRID_WIDTH-1, oy ) -- Right end + + outlines:refresh() end -- ------------------------------------------------ @@ -52,24 +66,19 @@ function HealthScreen.new() function self:init( ncharacter ) tw, th = TexturePacks.getTileDimensions() + x, y = GridHelper.centerElement( UI_GRID_WIDTH, UI_GRID_HEIGHT ) character = ncharacter; characterType = character:getBody():getID(); - px = math.floor( love.graphics.getWidth() / tw ) * 0.5 - math.floor( SCREEN_WIDTH * 0.5 ) - py = math.floor( love.graphics.getHeight() / th ) * 0.5 - math.floor( SCREEN_HEIGHT * 0.5 ) - px, py = px * tw, py * th + background = UIBackground.new( x, y, 0, 0, UI_GRID_WIDTH, UI_GRID_HEIGHT ) - outlines = Outlines.new() - createOutlines( SCREEN_WIDTH, SCREEN_HEIGHT ) - outlines:refresh() + generateOutlines() end function self:draw() - TexturePacks.setColor( 'sys_background' ) - love.graphics.rectangle( 'fill', px, py, SCREEN_WIDTH * tw, SCREEN_HEIGHT * th ) - - outlines:draw( px, py ) + background:draw() + outlines:draw() local counter = 3; for _, bodyPart in pairs( character:getBody():getBodyParts() ) do @@ -92,8 +101,8 @@ function HealthScreen.new() TexturePacks.setColor( 'ui_health_fine_limb' ) status = 'FINE' end - love.graphics.print( Translator.getText( bodyPart:getID() ), px + tw, py + th * counter ) - love.graphics.printf( status, px + tw, py + th * counter, ( SCREEN_WIDTH - 2 ) * tw, 'right' ) + love.graphics.print( Translator.getText( bodyPart:getID() ), (x+1) * tw, (y+counter) * th ) + love.graphics.printf( status, (x+1) * tw, (y+counter) * th, ( UI_GRID_WIDTH - 2 ) * tw, 'right' ) if bodyPart:isBleeding() then local str = string.format( 'Bleeding %1.2f', bodyPart:getBloodLoss() ); @@ -106,7 +115,7 @@ function HealthScreen.new() elseif bodyPart:getHealth() / bodyPart:getMaxHealth() < 1.0 then TexturePacks.setColor( 'ui_health_bleeding_bad' ) end - love.graphics.printf( str, px + tw, py + th * counter, ( SCREEN_WIDTH - 2 ) * tw, 'center' ) + love.graphics.printf( str, (x+1) * tw, (y+counter) * th, ( UI_GRID_WIDTH - 2 ) * tw, 'center' ) end end end @@ -115,12 +124,12 @@ function HealthScreen.new() -- Draw character type. local type = Translator.getText( 'ui_character_type' ) .. Translator.getText( characterType ) - love.graphics.print( type, px + tw, py + th ) + love.graphics.print( type, (x+1) * tw, (y+1) * th ) -- Draw character name. if character:getName() then local name = Translator.getText( 'ui_character_name' ) .. character:getName() - love.graphics.print( name, px + 2 * tw + TexturePacks.getFont():measureWidth( type ), py + th ) + love.graphics.print( name, (x+2) * tw + TexturePacks.getFont():measureWidth( type ), (y+1) * th ) end TexturePacks.resetColor() @@ -132,13 +141,6 @@ function HealthScreen.new() end end - function self:resize( sx, sy ) - px = math.floor( sx / tw ) * 0.5 - math.floor( SCREEN_WIDTH * 0.5 ) - py = math.floor( sy / th ) * 0.5 - math.floor( SCREEN_HEIGHT * 0.5 ) - px, py = px * tw, py * th - Log.debug( string.format( "Adjust position for Health Screen -> %d (%d), %d (%d)", sx, px, sy, py )); - end - return self; end diff --git a/src/ui/screens/HelpScreen.lua b/src/ui/screens/HelpScreen.lua index a642e9cf..0e25f34c 100644 --- a/src/ui/screens/HelpScreen.lua +++ b/src/ui/screens/HelpScreen.lua @@ -1,67 +1,174 @@ -local ScreenManager = require( 'lib.screenmanager.ScreenManager' ); -local Screen = require( 'lib.screenmanager.Screen' ); +local ScreenManager = require( 'lib.screenmanager.ScreenManager' ) +local Screen = require( 'lib.screenmanager.Screen' ) local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) +local GridHelper = require( 'src.util.GridHelper' ) +local UIBackground = require( 'src.ui.elements.UIBackground' ) +local UIOutlines = require( 'src.ui.elements.UIOutlines' ) +local Translator = require( 'src.util.Translator' ) -- ------------------------------------------------ -- Module -- ------------------------------------------------ -local HelpScreen = {}; +local HelpScreen = {} + +-- ------------------------------------------------ +-- Constants +-- ------------------------------------------------ + +local UI_GRID_WIDTH = 64 +local UI_GRID_HEIGHT = 48 + +local HELP_TEXT = { + { + text = 'CHARACTERS', + color = 'ui_help_section', + children = { + 'NOTE: Characters can also be selected by right-clicking on them!', + 'backspace - Select previous character', + 'space - Select next character', + 'return - End turn', + 'i - Open inventory', + 'h - Open health panel' + } + }, + { + text = 'WEAPONS', + color = 'ui_help_section', + children = { + 'left - select previous firing mode', + 'right - select next firing mode', + 'r - reload current weapon' + } + }, + { + text = 'STANCES', + color = 'ui_help_section', + children = { + 's - change stance to Stand', + 'c - change stance to Crouch', + 'p - change stance to Prone' + } + }, + { + text = 'INPUT', + color = 'ui_help_section', + children = { + 'a - Switch to Attack Mode', + 'm - Switch to Movement Mode', + 'e - Switch to Interaction Mode (e.g. to open barrels or doors)' + } + }, + { + text = 'MISC', + color = 'ui_help_section', + children = { + 'f - Switch between windowed and fullscreen modes' + } + } +} -- ------------------------------------------------ -- Constructor -- ------------------------------------------------ function HelpScreen.new() - local self = Screen.new(); - - local t = { - 'CHARACTERS', - ' NOTE: Characters can also be selected by right-clicking on them!', - ' backspace - Select previous character', - ' space - Select next character', - ' return - End turn', - ' i - Open inventory', - ' h - Open health panel', - '', - 'WEAPONS', - ' left - select previous firing mode', - ' right - select next firing mode', - ' r - reload current weapon', - '', - 'STANCES', - ' s - change stance to Stand', - ' c - change stance to Crouch', - ' p - change stance to Prone', - '', - 'INPUT MODES', - ' a - Switch to Attack Mode', - ' m - Switch to Movement Mode', - ' e - Switch to Interaction Mode (e.g. to open barrels or doors)', - '', - 'MISC', - ' f - Switch between windowed and fullscreen modes' - } + local self = Screen.new() + + -- ------------------------------------------------ + -- Private Attributes + -- ------------------------------------------------ + + local x, y + local header + local background + local outlines + local text + + -- ------------------------------------------------ + -- Private Methods + -- ------------------------------------------------ + + --- + -- Wraps the text into a Text object. + -- + local function assembleText() + text = love.graphics.newText( TexturePacks.getFont():get() ) + + local tw, th = TexturePacks.getTileDimensions() + local offset = 0 + + -- Draw sections first. + for i = 1, #HELP_TEXT do + text:addf({ TexturePacks.getColor( HELP_TEXT[i].color ), HELP_TEXT[i].text }, (UI_GRID_WIDTH-2) * tw, 'left', 0, offset * th ) + offset = offset + 1 + + -- Draw sub items with a slight offset to the right. + for j = 1, #HELP_TEXT[i].children do + offset = offset + 1 + text:addf( HELP_TEXT[i].children[j], (UI_GRID_WIDTH-2) * tw, 'left', 4*tw, offset * th ) + end + + offset = offset + 2 + end + end + + --- + -- Generates the outlines for this screen. + -- + local function generateOutlines() + outlines = UIOutlines.new( x, y, 0, 0, UI_GRID_WIDTH, UI_GRID_HEIGHT ) + + -- Horizontal borders. + for ox = 0, UI_GRID_WIDTH-1 do + outlines:add( ox, 0 ) -- Top + outlines:add( ox, 2 ) -- Header + outlines:add( ox, UI_GRID_HEIGHT-1 ) -- Bottom + end + + -- Vertical outlines. + for oy = 0, UI_GRID_HEIGHT-1 do + outlines:add( 0, oy ) -- Left + outlines:add( UI_GRID_WIDTH-1, oy ) -- Right + end + + outlines:refresh() + end + + -- ------------------------------------------------ + -- Public Methods + -- ------------------------------------------------ + + function self:init() + x, y = GridHelper.centerElement( UI_GRID_WIDTH, UI_GRID_HEIGHT ) + + background = UIBackground.new( x, y, 0, 0, UI_GRID_WIDTH, UI_GRID_HEIGHT ) + + generateOutlines() + + assembleText() + + header = love.graphics.newText( TexturePacks.getFont():get(), Translator.getText( 'ui_help_header' )) + end function self:draw() - TexturePacks.setColor( 'sys_background' ) - love.graphics.rectangle( 'fill', 5, 5, love.graphics.getWidth() - 5, love.graphics.getHeight() - 5 ); + background:draw() + outlines:draw() + + local tw, th = TexturePacks.getTileDimensions() TexturePacks.setColor( 'ui_text' ) - love.graphics.rectangle( 'line', 5, 5, love.graphics.getWidth() - 5, love.graphics.getHeight() - 5 ); + love.graphics.draw( header, (x+1) * tw, (y+1) * th ) + love.graphics.draw( text, (x+1) * tw, (y+3) * th ) TexturePacks.resetColor() - - for i, line in ipairs( t ) do - love.graphics.print( line, 20, 20 * i ); - end end function self:keypressed( key ) if key == 'escape' then - ScreenManager.pop(); + ScreenManager.pop() end end - return self; + return self end -return HelpScreen; +return HelpScreen diff --git a/src/ui/screens/IngameBaseMenu.lua b/src/ui/screens/IngameBaseMenu.lua index 97ed4879..e34364df 100644 --- a/src/ui/screens/IngameBaseMenu.lua +++ b/src/ui/screens/IngameBaseMenu.lua @@ -1,11 +1,14 @@ local Screen = require( 'lib.screenmanager.Screen' ); local ScreenManager = require( 'lib.screenmanager.ScreenManager' ); local Button = require( 'src.ui.elements.Button' ); +local Label = require( 'src.ui.elements.Label' ) local VerticalList = require( 'src.ui.elements.VerticalList' ); local SaveHandler = require( 'src.SaveHandler' ); local Translator = require( 'src.util.Translator' ); -local Outlines = require( 'src.ui.elements.Outlines' ) +local UIOutlines = require( 'src.ui.elements.UIOutlines' ) +local UIBackground = require( 'src.ui.elements.UIBackground' ) local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) +local GridHelper = require( 'src.util.GridHelper' ) -- ------------------------------------------------ -- Module @@ -17,8 +20,8 @@ local IngameBaseMenu = {} -- Constants -- ------------------------------------------------ -local SCREEN_WIDTH = 8; -local SCREEN_HEIGHT = 7; +local UI_GRID_WIDTH = 10 +local UI_GRID_HEIGHT = 7 -- ------------------------------------------------ -- Constructor @@ -33,26 +36,37 @@ function IngameBaseMenu.new() local game; local buttonList; + local header + local background local outlines - local px, py; + local x, y local tw, th -- ------------------------------------------------ - -- Private Functions + -- Private Methods -- ------------------------------------------------ - local function createOutlines( w, h ) - for x = 0, w - 1 do - for y = 0, h - 1 do - if x == 0 or x == (w - 1) or y == 0 or y == (h - 1) then - outlines:add( x, y ) - end - if y == 2 then - outlines:add( x, y ) - end - end + --- + -- Generates the outlines for this screen. + -- + local function generateOutlines() + outlines = UIOutlines.new( x, y, 0, 0, UI_GRID_WIDTH, UI_GRID_HEIGHT ) + + -- Horizontal borders. + for ox = 0, UI_GRID_WIDTH-1 do + outlines:add( ox, 0 ) -- Top + outlines:add( ox, 2 ) -- Header + outlines:add( ox, UI_GRID_HEIGHT-1 ) -- Bottom end + + -- Vertical outlines. + for oy = 0, UI_GRID_HEIGHT-1 do + outlines:add( 0, oy ) -- Left + outlines:add( UI_GRID_WIDTH-1, oy ) -- Right + end + + outlines:refresh() end local function saveGame() @@ -69,8 +83,7 @@ function IngameBaseMenu.new() end local function createButtons() - local x, y = px, py; - buttonList = VerticalList.new( x, y + 3 * th, SCREEN_WIDTH * tw, th ) + buttonList = VerticalList.new( x * tw, (y+3) * th, UI_GRID_WIDTH * tw, th ) buttonList:addElement( Button.new( Translator.getText( 'ui_ingame_save_game' ), saveGame )) buttonList:addElement( Button.new( Translator.getText( 'ui_ingame_open_help' ), openHelpScreen )) buttonList:addElement( Button.new( Translator.getText( 'ui_ingame_exit' ), exitToMainMenu )) @@ -81,29 +94,26 @@ function IngameBaseMenu.new() -- ------------------------------------------------ function self:init( ngame ) - tw, th = TexturePacks.getTileDimensions() - game = ngame; - px = math.floor( love.graphics.getWidth() / tw ) * 0.5 - math.floor( SCREEN_WIDTH * 0.5 ) - py = math.floor( love.graphics.getHeight() / th ) * 0.5 - math.floor( SCREEN_HEIGHT * 0.5 ) - px, py = px * tw, py * th + tw, th = TexturePacks.getTileDimensions() + x, y = GridHelper.centerElement( UI_GRID_WIDTH, UI_GRID_HEIGHT ) - outlines = Outlines.new() - createOutlines( SCREEN_WIDTH, SCREEN_HEIGHT ) - outlines:refresh() + background = UIBackground.new( x, y, 0, 0, UI_GRID_WIDTH, UI_GRID_HEIGHT ) + + generateOutlines() createButtons(); + + header = Label.new( Translator.getText( 'ui_ingame_paused' ), 'ui_label', 'center' ) end function self:draw() - TexturePacks.setColor( 'sys_background' ); - love.graphics.rectangle( 'fill', px, py, SCREEN_WIDTH * tw, SCREEN_HEIGHT * th ) - - outlines:draw( px, py ) + background:draw() + outlines:draw() buttonList:draw(); - love.graphics.printf( Translator.getText( 'ui_ingame_paused' ), px + tw, py + th, (SCREEN_WIDTH - 2) * tw, 'center' ) + header:draw( (x+1) * tw, (y+1) * th, (UI_GRID_WIDTH - 2) * tw, th ) end function self:update() @@ -126,12 +136,6 @@ function IngameBaseMenu.new() buttonList:mousereleased(); end - function self:resize( sx, sy ) - px = math.floor( sx / tw ) * 0.5 - math.floor( SCREEN_WIDTH * 0.5 ) - py = math.floor( sy / th ) * 0.5 - math.floor( SCREEN_HEIGHT * 0.5 ) - px, py = px * tw, py * th - end - return self; end diff --git a/src/ui/screens/IngameCombatMenu.lua b/src/ui/screens/IngameCombatMenu.lua index c4fdfde1..52f86fa3 100644 --- a/src/ui/screens/IngameCombatMenu.lua +++ b/src/ui/screens/IngameCombatMenu.lua @@ -4,8 +4,10 @@ local Button = require( 'src.ui.elements.Button' ); local VerticalList = require( 'src.ui.elements.VerticalList' ); local SaveHandler = require( 'src.SaveHandler' ); local Translator = require( 'src.util.Translator' ); -local Outlines = require( 'src.ui.elements.Outlines' ) +local UIOutlines = require( 'src.ui.elements.UIOutlines' ) +local UIBackground = require( 'src.ui.elements.UIBackground' ) local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) +local GridHelper = require( 'src.util.GridHelper' ) -- ------------------------------------------------ -- Module @@ -17,8 +19,8 @@ local IngameCombatMenu = {} -- Constants -- ------------------------------------------------ -local SCREEN_WIDTH = 14; -local SCREEN_HEIGHT = 8; +local UI_GRID_WIDTH = 14 +local UI_GRID_HEIGHT = 8 -- ------------------------------------------------ -- Constructor @@ -34,25 +36,35 @@ function IngameCombatMenu.new() local game; local buttonList; + local background local outlines - local px, py; + local x, y local tw, th -- ------------------------------------------------ - -- Private Functions + -- Private Methods -- ------------------------------------------------ - local function createOutlines( w, h ) - for x = 0, w - 1 do - for y = 0, h - 1 do - if x == 0 or x == (w - 1) or y == 0 or y == (h - 1) then - outlines:add( x, y ) - end - if y == 2 then - outlines:add( x, y ) - end - end + --- + -- Generates the outlines for this screen. + -- + local function generateOutlines() + outlines = UIOutlines.new( x, y, 0, 0, UI_GRID_WIDTH, UI_GRID_HEIGHT ) + + -- Horizontal borders. + for ox = 0, UI_GRID_WIDTH-1 do + outlines:add( ox, 0 ) -- Top + outlines:add( ox, 2 ) -- Header + outlines:add( ox, UI_GRID_HEIGHT-1 ) -- Bottom end + + -- Vertical outlines. + for oy = 0, UI_GRID_HEIGHT-1 do + outlines:add( 0, oy ) -- Left + outlines:add( UI_GRID_WIDTH-1, oy ) -- Right + end + + outlines:refresh() end local function saveGame() @@ -75,8 +87,7 @@ function IngameCombatMenu.new() end local function createButtons() - local x, y = px, py; - buttonList = VerticalList.new( x, y + 3 * th, SCREEN_WIDTH * tw, th ) + buttonList = VerticalList.new( x*tw, (y+3) * th, UI_GRID_WIDTH * tw, th ) buttonList:addElement( Button.new( Translator.getText( 'ui_ingame_save_game' ), saveGame )) buttonList:addElement( Button.new( Translator.getText( 'ui_ingame_open_help' ), openHelpScreen )) buttonList:addElement( Button.new( Translator.getText( 'ui_ingame_abort_mission' ), exitToBase )) @@ -88,29 +99,24 @@ function IngameCombatMenu.new() -- ------------------------------------------------ function self:init( ngame ) - tw, th = TexturePacks.getTileDimensions() - game = ngame; - px = math.floor( love.graphics.getWidth() / tw ) * 0.5 - math.floor( SCREEN_WIDTH * 0.5 ) - py = math.floor( love.graphics.getHeight() / th ) * 0.5 - math.floor( SCREEN_HEIGHT * 0.5 ) - px, py = px * tw, py * th + tw, th = TexturePacks.getTileDimensions() + x, y = GridHelper.centerElement( UI_GRID_WIDTH, UI_GRID_HEIGHT ) - outlines = Outlines.new() - createOutlines( SCREEN_WIDTH, SCREEN_HEIGHT ) - outlines:refresh() + background = UIBackground.new( x, y, 0, 0, UI_GRID_WIDTH, UI_GRID_HEIGHT ) + + generateOutlines() createButtons(); end function self:draw() - TexturePacks.setColor( 'sys_background' ); - love.graphics.rectangle( 'fill', px, py, SCREEN_WIDTH * tw, SCREEN_HEIGHT * th ) - - outlines:draw( px, py ) + background:draw() + outlines:draw() buttonList:draw(); - love.graphics.printf( Translator.getText( 'ui_ingame_paused' ), px + tw, py + th, (SCREEN_WIDTH - 2) * tw, 'center' ) + love.graphics.printf( Translator.getText( 'ui_ingame_paused' ), (x+1) * tw, (y+1) * th, (UI_GRID_WIDTH - 2) * tw, 'center' ) end function self:update() @@ -133,12 +139,6 @@ function IngameCombatMenu.new() buttonList:mousereleased(); end - function self:resize( sx, sy ) - px = math.floor( sx / tw ) * 0.5 - math.floor( SCREEN_WIDTH * 0.5 ) - py = math.floor( sy / th ) * 0.5 - math.floor( SCREEN_HEIGHT * 0.5 ) - px, py = px * tw, py * th - end - return self; end diff --git a/src/ui/screens/InventoryScreen.lua b/src/ui/screens/InventoryScreen.lua index e237734f..eba823de 100644 --- a/src/ui/screens/InventoryScreen.lua +++ b/src/ui/screens/InventoryScreen.lua @@ -1,202 +1,292 @@ -local ScreenManager = require( 'lib.screenmanager.ScreenManager' ); -local Screen = require( 'lib.screenmanager.Screen' ); -local UIInventoryList = require( 'src.ui.inventory.UIInventoryList' ); -local UIEquipmentList = require( 'src.ui.inventory.UIEquipmentList' ); -local ScrollArea = require( 'src.ui.inventory.ScrollArea' ); -local ItemStats = require( 'src.ui.inventory.ItemStats' ); -local Translator = require( 'src.util.Translator' ); -local Outlines = require( 'src.ui.elements.Outlines' ) -local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) +--- +-- @module InventoryScreen +-- + +-- ------------------------------------------------ +-- Required Modules +-- ------------------------------------------------ + +local Screen = require( 'lib.screenmanager.Screen' ) +local ScreenManager = require( 'lib.screenmanager.ScreenManager' ) +local UIOutlines = require( 'src.ui.elements.UIOutlines' ) +local UIBackground = require( 'src.ui.elements.UIBackground' ) +local UIEquipmentList = require( 'src.ui.elements.inventory.UIEquipmentList' ) +local UIInventoryList = require( 'src.ui.elements.inventory.UIInventoryList' ) +local UITranslatedLabel = require( 'src.ui.elements.UITranslatedLabel' ) +local UIInventoryDragboard = require( 'src.ui.elements.inventory.UIInventoryDragboard' ) +local UIItemStats = require( 'src.ui.elements.inventory.UIItemStats' ) +local GridHelper = require( 'src.util.GridHelper' ) -- ------------------------------------------------ -- Module -- ------------------------------------------------ -local InventoryScreen = {}; +local InventoryScreen = {} + +-- ------------------------------------------------ +-- Constants +-- ------------------------------------------------ + +-- The dimensions of the whole inventory screen. +local UI_GRID_WIDTH = 64 +local UI_GRID_HEIGHT = 48 + +-- The width and height of the inventory columns. +local COLUMN_WIDTH = 20 +local COLUMN_HEIGHT = 44 + +-- The width and hight of the item equipment column. +local EQUIPMENT_WIDTH = COLUMN_WIDTH +local EQUIPMENT_HEIGHT = 12 + +-- The width and hight of the item stats column. +local ITEM_STATS_WIDTH = COLUMN_WIDTH +local ITEM_STATS_HEIGHT = COLUMN_HEIGHT - EQUIPMENT_HEIGHT - 1 -- ------------------------------------------------ -- Constructor -- ------------------------------------------------ function InventoryScreen.new() - local self = Screen.new(); + local self = Screen.new() - local character; - local lists; - local dragboard; - local target; + local x, y - local w, h; - local sx, sy; -- Spacers. - local itemDescriptionSpacer; -- Spacers. - local tw, th + local outlines + local background - local itemDescriptionArea; - local itemStatsArea; + local lists + local listLabels - local outlines + local itemStats + + local dragboard -- ------------------------------------------------ -- Private Methods -- ------------------------------------------------ - local function getColumnWidth() - return ( sx - 1 ) * tw - end + --- + -- Creates the outlines for the Inventory window. + -- + local function generateOutlines() + outlines = UIOutlines.new( x, y, 0, 0, UI_GRID_WIDTH, UI_GRID_HEIGHT ) - local function getEquipmentColumnOffset() - return tw - end + -- Horizontal borders. + for ox = 0, UI_GRID_WIDTH-1 do + outlines:add( ox, 0 ) -- Top + outlines:add( ox, UI_GRID_HEIGHT-1 ) -- Bottom + end - local function getOtherColumnOffset() - return ( 2 + 2 * sx ) * tw - end + -- Horizontal line below Equipment list. + -- Offset calculations: + -- y-axis: Outline top + Header + Outline Header + EQUIPMENT_HEIGHT => EQUIPMENT_HEIGHT+3 + for ox = 0, COLUMN_WIDTH do + outlines:add( ox, EQUIPMENT_HEIGHT+3 ) + end - local function getInventoryColumnOffset() - return ( 2 + sx ) * tw - end + -- Vertical outlines. + for oy = 0, UI_GRID_HEIGHT-1 do + outlines:add( 0, oy ) -- Left + outlines:add( UI_GRID_WIDTH-1, oy ) -- Right + end - local function getListBelowCursor() - for _, list in pairs( lists ) do - if list:isMouseOver() then - return list; - end + -- Vertical line after the equipment column. + -- Offset calculations: + -- x:axis: Outline left + First column => 1 + COLUMN_WIDTH + for oy = 0, UI_GRID_HEIGHT-1 do + outlines:add( 1+COLUMN_WIDTH, oy ) end - return false; + + -- Vertical line after the inventory column. + -- Offset calculations: + -- x-axis: Outline left + First column + Outline + Second Column + -- => 1 + COLUMN_WIDTH + 1 + COLUMN_WIDTH + -- => 2 + 2*COLUMN_WIDTH + for oy = 0, UI_GRID_HEIGHT-1 do + outlines:add( 2 + 2*COLUMN_WIDTH, oy ) + end + + -- Horizontal line for column headers. + -- Offset calculations: + -- x-axis: Outline top + Header line => 2 + for ox = 0, UI_GRID_WIDTH-1 do + outlines:add( ox, 2 ) + end + + outlines:refresh() end - local function createEquipment() - lists.equipment = UIEquipmentList.new( getEquipmentColumnOffset(), 3 * th, sx * tw, 'inventory_equipment', character ) - lists.equipment:init(); + --- + -- Creates the equipment list for the currently selected character and the + -- associated header label. + -- @tparam Character character The character to use for the equipment list. + -- + local function createEquipmentList( character ) + -- Offset calculations: + -- x-axis: Outline left => 1 + -- y-axis: Outline top + Header Text + Outline below Header => 3 + local ox, oy = 1, 3 + lists.equipment = UIEquipmentList.new( x, y, ox, oy, EQUIPMENT_WIDTH, EQUIPMENT_HEIGHT ) + lists.equipment:init( character ) + + -- Offset calculations: + -- x-axis: Outline left => 1 + -- y-axis: Outline top => 1 + local lx, ly = 1, 1 + listLabels.equipment = UITranslatedLabel.new( x, y, lx, ly, EQUIPMENT_WIDTH, 1, 'inventory_equipment', 'ui_inventory_headers' ) end - local function createInventory() - lists.inventory = UIInventoryList.new( getInventoryColumnOffset(), 3 * th, getColumnWidth(), 'inventory_inventory', character:getInventory() ) - lists.inventory:init(); + --- + -- Creates the character's "backpack" inventory in which all items he carries + -- are stored and the associated header label. + -- @tparam Character character The character to use for the equipment list. + -- + local function createCharacterInventoryList( character ) + -- Offset calculations: + -- x-axis: Outline left + Equipment Column + Equipment Column Outline + -- => 1 + COLUMN_WIDTH + 1 + -- => 2 + COLUMN_WIDTH + -- y-axis: Outline top + Header Text + Outline below Header => 3 + local ox, oy = 2 + COLUMN_WIDTH, 3 + lists.characterInventory = UIInventoryList.new( x, y, ox, oy, COLUMN_WIDTH, COLUMN_HEIGHT ) + lists.characterInventory:init( character:getInventory() ) + + -- Label coordinates relative to the screen's coordinates. + -- x-axis: Outline left + Equipment Column + Equipment Column Outline + -- => 1 + COLUMN_WIDTH + 1 + -- => 2 + COLUMN_WIDTH + -- y-axis: Outline top => 1 + local lx, ly = 2 + COLUMN_WIDTH, 1 + listLabels.characterInventory = UITranslatedLabel.new( x, y, lx, ly, COLUMN_WIDTH, 1, 'inventory_character', 'ui_inventory_headers' ) end - local function createOtherInventory() - local x, y, cw = getOtherColumnOffset(), 3 * th, getColumnWidth() + --- + -- Creates the target inventory with which the character wants to interact. + -- and the associated header label. + -- @tparam Character character The character to use for the equipment list. + -- @tparam Tile target The target tile to interact with. + -- + local function createTargetInventoryList( character, target ) + local id, inventory + -- TODO How to handle base inventory? if target:instanceOf( 'Inventory' ) then - lists.other = UIInventoryList.new( x, y, cw, 'inventory_base', target ) - lists.other:init() - return - end - - -- Create inventory for container world objects. - if target:hasWorldObject() and target:getWorldObject():isContainer() then - lists.other = UIInventoryList.new( x, y, cw, 'inventory_container_inventory', target:getWorldObject():getInventory() ); - lists.other:init(); - return; + id, inventory = 'inventory_base', target + elseif target:hasWorldObject() and target:getWorldObject():isContainer() then + id, inventory = 'inventory_container_inventory', target:getWorldObject():getInventory() + elseif target:isOccupied() and target:getCharacter() ~= character and target:getCharacter():getFaction():getType() == character:getFaction():getType() then + id, inventory = 'inventory_character', target:getCharacter():getInventory() + else + id, inventory = 'inventory_tile_inventory', target:getInventory() end - -- Create inventory for other characters of the same faction. - if target:isOccupied() and target:getCharacter() ~= character and target:getCharacter():getFaction():getType() == character:getFaction():getType() then - lists.other = UIInventoryList.new( x, y, cw, 'inventory_inventory', target:getCharacter():getInventory() ); - lists.other:init(); - return; - end + -- Offset calculations: + -- x-axis: Outline left + Equipment Column + Equipment Column Outline + -- + Character Inventory Column + Character Inventory Column Outline + -- => 1 + COLUMN_WIDTH + 1 + COLUMN_WIDTH + 1 + -- => 3 + 2 * COLUMN_WIDTH + -- y-axis: Outline top + Header Text + Outline below Header => 3 + local ox, oy = 3 + 2 * COLUMN_WIDTH, 3 + lists.targetInventory = UIInventoryList.new( x, y, ox, oy, COLUMN_WIDTH, COLUMN_HEIGHT ) + lists.targetInventory:init( inventory ) + + -- Offset calculations: + -- x-axis: Outline left + Equipment Column + Equipment Column Outline + -- + Character Inventory Column + Character Inventory Column Outline + -- => 1 + COLUMN_WIDTH + 1 + COLUMN_WIDTH + 1 + -- => 3 + 2 * COLUMN_WIDTH + -- y-axis: Outline top => 1 + local lx, ly = 3 + 2 * COLUMN_WIDTH, 1 + listLabels.targetInventory = UITranslatedLabel.new( x, y, lx, ly, COLUMN_WIDTH, 1, id, 'ui_inventory_headers' ) + end - -- Create inventory for a tile's floor. - lists.other = UIInventoryList.new( x, y, cw, 'inventory_tile_inventory', target:getInventory() ); - lists.other:init(); + --- + -- Creates the equipment and inventory lists. + -- @tparam Character character The character to use for the equipment list. + -- @tparam Tile target The target tile to interact with. + -- + local function createInventoryLists( character, target ) + lists = {} + listLabels = {} + createEquipmentList( character ) + createCharacterInventoryList( character ) + createTargetInventoryList( character, target ) end - local function returnItemToOrigin( item, origin ) - if origin:instanceOf( 'EquipmentSlot' ) then - character:getEquipment():addItem( origin, item ) - else - origin:drop( item ); + --- + -- Returns the list the mouse is currently over. + -- @return The equipment or inventory list the mouse is over. + -- + local function getListBelowCursor() + for _, list in pairs( lists ) do + if list:isMouseOver() then + return list + end end end - local function drag( list, button ) - if not list then - return; + --- + -- Refreshes all inventor lists. + -- + local function refreshLists() + for _, list in pairs( lists ) do + list:refresh() end + end - local item, slot = list:drag( button == 2, love.keyboard.isDown( 'lshift' )); - if item then - -- If we have an actual item slot we use it as the origin to - -- which the item is returned in case it can't be dropped anywhere. - dragboard = { item = item, origin = slot or list }; - if item:instanceOf( 'Container' ) then - createInventory(); - end + --- + -- Initiates a drag action. + -- @tparam number button The mouse button index. + -- + local function drag( button ) + -- Can't drag if we are already dragging. + if dragboard:hasDragContext() then + return end - end - local function drop( list ) + -- Can't drag if not over a list. + local list = getListBelowCursor() if not list then - returnItemToOrigin( dragboard.item, dragboard.origin ); - else - local success = list:drop( dragboard.item, dragboard.origin ); - if not success then - returnItemToOrigin( dragboard.item, dragboard.origin ); - end + return end - createInventory(); - dragboard = nil; - end - local function drawHeaders() - love.graphics.print( lists.equipment:getLabel(), getEquipmentColumnOffset(), th ) + local item, slot = list:drag( button == 2, love.keyboard.isDown( 'lshift' )) - if lists.inventory then - love.graphics.print( lists.inventory:getLabel(), getInventoryColumnOffset(), th ) - else - love.graphics.print( 'No inventory equipped', getInventoryColumnOffset(), th ) + -- Abort if there is nothing to drag here. + if not item then + return end - love.graphics.print( lists.other:getLabel(), getOtherColumnOffset(), th ) + -- Display stats for the dragged item. + itemStats:setItem( item ) - love.graphics.print( 'Item Description', getEquipmentColumnOffset(), ( 1 + 2 * sy ) * th ) - love.graphics.print( 'Item Attributes', ( itemDescriptionSpacer + 1 ) * tw, ( 1 + 2 * sy ) * th ) - end + -- If we have an actual item slot we use it as the origin to + -- which the item is returned in case it can't be dropped anywhere. + dragboard:drag( item, slot or list ) - local function updateScreenDimensions( nw, nh ) - w = math.floor( nw / tw ) - h = math.floor( nh / th ) - sx = math.floor( w / 3 ); - sy = math.floor( h / 3 ); - itemDescriptionSpacer = math.floor( w / 2 ); + -- If the dragged item is a container we need to refresh the inventory lists + -- because it changes the inventory volumes. + if item:instanceOf( 'Container' ) then + refreshLists() + end end - local function createOutlines() - for x = 0, w - 1 do - for y = 0, h - 1 do - -- Draw borders. - if x == 0 or x == (w - 1) or y == 0 or y == (h - 1) then - outlines:add( x, y ) - end - - -- Draw vertical column lines. - if ( x == 1 + sx or x == 1 + 2 * sx ) and ( y < 2 * sy ) then - outlines:add( x, y ) - end - - -- Draw bottom line of the column headers. - if y == 2 then - outlines:add( x, y ) - end - - -- Draw the horizontal line below the inventory columns. - if y == 2 * sy then - outlines:add( x, y ) - end - - -- Draw item description separator. - if x == itemDescriptionSpacer and y > 2 * sy then - outlines:add( x, y ) - end - - -- Draw horizontal line for item stats and description. - if y == ( 2 * sy + 2 ) then - outlines:add( x, y ) - end - end + --- + -- Selects an item for the item stats area. + -- + local function selectItem() + local list = getListBelowCursor() + if not list then + return + end + + local item = list:getItemBelowCursor() + if not item then + return end + + itemStats:setItem( item ) end -- ------------------------------------------------ @@ -204,144 +294,106 @@ function InventoryScreen.new() -- ------------------------------------------------ --- - -- Creates the three inventory lists for the player's equipment, his inventory - -- and the tile he is standing on. - -- @param ncharacter (Character) The character to open the inventory for. - -- @param ntarget (Tile) The target tile to open the inventory for. + -- Initialises the inventory screen. + -- @tparam Character ncharacter The character to open the inventory for. + -- @tparam Tile ntarget The target tile to open the inventory for. -- - function self:init( ncharacter, ntarget ) - tw, th = TexturePacks.getTileDimensions() + function self:init( character, target ) + love.mouse.setVisible( true ) - character = ncharacter; - target = ntarget; + x, y = GridHelper.centerElement( UI_GRID_WIDTH, UI_GRID_HEIGHT ) - love.mouse.setVisible( true ); - updateScreenDimensions( love.graphics.getDimensions() ); + -- General UI Elements. + background = UIBackground.new( x, y, 0, 0, UI_GRID_WIDTH, UI_GRID_HEIGHT ) + generateOutlines() - outlines = Outlines.new(); - createOutlines() - outlines:refresh() - - itemDescriptionArea = ScrollArea.new( 1, 3 + 2 * sy, itemDescriptionSpacer - 1, sy - 3 ); - itemStatsArea = ItemStats.new( itemDescriptionSpacer + 1, 3 + 2 * sy, itemDescriptionSpacer - 2, sy - 3 ); + -- UI inventory lists. + createInventoryLists( character, target ) - lists = {}; + dragboard = UIInventoryDragboard.new() - createEquipment(); - createInventory(); - createOtherInventory(); + -- Add the item stats area which displays the item attributes and a description area. + -- x-axis: Outline left => 1 + -- y-axis: Outline top + Header + Outl ine Header + -- + Outline below equipment + EQUIPMENT_HEIGHT + -- => EQUIPMENT_HEIGHT+4 + itemStats = UIItemStats.new( x, y, 1, EQUIPMENT_HEIGHT+4, ITEM_STATS_WIDTH, ITEM_STATS_HEIGHT ) end --- - -- Draws the inventory lists and the dragged item (if there is one). + -- Draws the inventory screen. -- function self:draw() - TexturePacks.setColor( 'sys_background' ) - love.graphics.rectangle( 'fill', 0, 0, love.graphics.getDimensions() ); - TexturePacks.resetColor() - - outlines:draw( 0, 0 ) - drawHeaders( love.graphics.getWidth() ); + background:draw() + outlines:draw() for _, list in pairs( lists ) do - list:draw(); - if list:isMouseOver() then - local item = list:getItemBelowCursor(); - if item then - itemDescriptionArea:setText( Translator.getText( item:getDescriptionID() )); - itemDescriptionArea:draw(); - - itemStatsArea:setItem( item ); - end - end + list:draw() end - if dragboard then - local mx, my = love.mouse.getPosition(); - - TexturePacks.setColor( 'ui_inventory_item' ) - for _, list in pairs( lists ) do - if list:isMouseOver() then - local di = dragboard.item; - if not list:doesFit( di ) then - TexturePacks.setColor( 'ui_inventory_full' ) - break; - end - end - end - - local item = dragboard.item; - local str = item and Translator.getText( item:getID() ) or Translator.getText( 'inventory_empty_slot' ); - if item:instanceOf( 'ItemStack' ) and item:getItemCount() > 1 then - str = string.format( '%s (%d)', str, item:getItemCount() ); - end - love.graphics.print( str, mx, my ); - TexturePacks.setColor( 'ui_text' ) - - itemDescriptionArea:setText( Translator.getText( item:getDescriptionID() )); - itemDescriptionArea:draw(); - + for _, label in pairs( listLabels ) do + label:draw() end - lists.equipment:highlightSlot( dragboard and dragboard.item ); - itemStatsArea:draw(); + -- Highlight equipment slot if draggable item can be put there. + lists.equipment:highlight( dragboard:getDragContext() and dragboard:getDraggedItem() ) + + itemStats:draw() + dragboard:draw( lists ) end --- - -- Updates the inventory lists. + -- This method is called when the inventory screen is closed. -- - function self:update( dt ) - if target:instanceOf( 'Tile' ) then - target:setDirty( true ) - end + function self:close() + love.mouse.setVisible( false ) - for _, list in pairs( lists ) do - list:update( dt ); + -- Drop any item that is currently dragged. + if dragboard:hasDragContext() then + dragboard:drop() end end + -- ------------------------------------------------ + -- Input Callbacks + -- ------------------------------------------------ + function self:keypressed( key ) - if key == 'up' then - itemDescriptionArea:scrollVertically( 1 ); - elseif key == 'down' then - itemDescriptionArea:scrollVertically( -1 ); - elseif key == 'escape' or key == 'i' then - ScreenManager.pop(); + if key == 'escape' or key == 'i' then + ScreenManager.pop() end + + itemStats:keypressed( key ) end - function self:mousepressed( _, _, button ) - if dragboard then - return; + function self:mousepressed( mx, my, button ) + local gx, gy = GridHelper.pixelsToGrid( mx, my ) + + if button == 2 then + selectItem() end + drag( button ) - local list = getListBelowCursor(); - drag( list, button ); + itemStats:mousepressed( gx, gy, button ) end function self:mousereleased( _, _, _ ) - if not dragboard then - return; + if not dragboard:hasDragContext() then + return end - local list = getListBelowCursor(); - drop( list ); - end + local list = getListBelowCursor() + dragboard:drop( list ) - function self:resize() - self:init( character, target ); + -- Refresh lists in case volumes have changed. + refreshLists() end - function self:close() - love.mouse.setVisible( false ); - - -- Drop any item that is currently dragged. - if dragboard then - drop(); - end + function self:wheelmoved( dx, dy ) + itemStats:wheelmoved( dx, dy ) end - return self; + return self end -return InventoryScreen; +return InventoryScreen diff --git a/src/ui/texturepacks/Font.lua b/src/ui/texturepacks/Font.lua index dbd5808d..d8550f26 100644 --- a/src/ui/texturepacks/Font.lua +++ b/src/ui/texturepacks/Font.lua @@ -57,6 +57,26 @@ function Font.new( source, glyphs, gwidth, gheight ) return font:getWidth( str ) end + --- + -- Aligns a string while taking the font's grid into account. + -- @tparam string alignMode Determines how the font is aligned (center, left or right). + -- @tparam string str The text to align. + -- @tparam number width The width of the area in which to align the string. + -- @treturn number The position at which to draw the string. + -- + function self:align( alignMode, str, width ) + if alignMode == 'center' then + local offset = width * 0.5 - font:getWidth( str ) * 0.5 + return math.floor( offset / gwidth ) * gwidth + elseif alignMode == 'left' then + return 0 + elseif alignMode == 'right' then + local offset = width - font:getWidth( str ) + return math.floor( offset / gwidth ) * gwidth + end + error( string.format( 'Invalid align mode "%s". Use "center", "left" or "right" instead.' ), alignMode ) + end + -- ------------------------------------------------ -- Getters -- ------------------------------------------------ diff --git a/src/ui/texturepacks/TexturePack.lua b/src/ui/texturepacks/TexturePack.lua index bc5c9df5..9791a3be 100644 --- a/src/ui/texturepacks/TexturePack.lua +++ b/src/ui/texturepacks/TexturePack.lua @@ -29,6 +29,7 @@ function TexturePack.new() local name local font + local glyphWidth, glyphHeight local tileset local colors @@ -42,6 +43,7 @@ function TexturePack.new() -- Generate font. local f = source.font font = Font.new( path .. f.source, f.glyphs.source, f.glyphs.width, f.glyphs.height ) + glyphWidth, glyphHeight = f.glyphs.width, f.glyphs.height -- Generate tileset. local t = source.tileset @@ -63,6 +65,10 @@ function TexturePack.new() return font end + function self:getGlyphDimensions() + return glyphWidth, glyphHeight + end + function self:getTileset() return tileset end diff --git a/src/ui/texturepacks/TexturePacks.lua b/src/ui/texturepacks/TexturePacks.lua index 9b91a98b..106b658f 100644 --- a/src/ui/texturepacks/TexturePacks.lua +++ b/src/ui/texturepacks/TexturePacks.lua @@ -164,6 +164,10 @@ function TexturePacks.getSprite( id, alt ) return texturePacks[current]:getTileset():getSprite( id, alt ) end +function TexturePacks.getGlyphDimensions() + return texturePacks[current]:getGlyphDimensions() +end + function TexturePacks.getTileDimensions() return texturePacks[current]:getTileset():getTileDimensions() end diff --git a/src/util/ArrayRotation.lua b/src/util/ArrayRotation.lua new file mode 100644 index 00000000..2ddf4458 --- /dev/null +++ b/src/util/ArrayRotation.lua @@ -0,0 +1,90 @@ +--- +-- Allows the rotation of two-dimensional arrays by 90, 180 and 270 degrees. +-- Works with square and non square arrays. +-- @module ArrayRotation +-- + +-- ------------------------------------------------ +-- Module +-- ------------------------------------------------ + +local ArrayRotation = {} + +-- ------------------------------------------------ +-- Private Functions +-- ------------------------------------------------ + +--- +-- Rotates a two dimensional array by 90°. +-- @tparam table arr The array to rotate. +-- @treturn table The rotated array. +-- +local function rotate90( arr ) + local rotatedArray = {} + for x = 1, #arr do + for y = 1, #arr[x] do + local r, c = #arr-(x-1), y + rotatedArray[c] = rotatedArray[c] or {} + rotatedArray[c][r] = arr[x][y] + end + end + return rotatedArray +end + +--- +-- Rotates a two dimensional array by 180°. +-- @tparam table arr The array to rotate. +-- @treturn table The rotated array. +-- +local function rotate180( arr ) + local rotatedArray = {} + for x = 1, #arr do + for y = 1, #arr[x] do + local r, c = #arr-(x-1), #arr[x]-(y-1) + rotatedArray[r] = rotatedArray[r] or {} + rotatedArray[r][c] = arr[x][y] + end + end + return rotatedArray +end + +--- +-- Rotates a two dimensional array by 270°. +-- @tparam table arr The array to rotate. +-- @treturn table The rotated array. +-- +local function rotate270( arr ) + local rotatedArray = {} + for x = 1, #arr do + for y = 1, #arr[x] do + local r, c = x, #arr[x]-(y-1) + rotatedArray[c] = rotatedArray[c] or {} + rotatedArray[c][r] = arr[x][y] + end + end + return rotatedArray +end + +-- ------------------------------------------------ +-- Public Functions +-- ------------------------------------------------ + +--- +-- Rotates a two dimensional array. +-- @tparam table arr The array to rotate. +-- @tparam number steps The amount of rotation to apply (0=0°, 1=90°, 2=180°, 3=270°) +-- @treturn table The rotated array. +-- +function ArrayRotation.rotate( arr, steps ) + if steps == 0 then + return arr + elseif steps == 1 then + return rotate90( arr ) + elseif steps == 2 then + return rotate180( arr ) + elseif steps == 3 then + return rotate270( arr ) + end +end + +return ArrayRotation diff --git a/src/util/GridHelper.lua b/src/util/GridHelper.lua new file mode 100644 index 00000000..ad7b7585 --- /dev/null +++ b/src/util/GridHelper.lua @@ -0,0 +1,41 @@ +--- +-- @module GridHelper +-- + +-- ------------------------------------------------ +-- Required Modules +-- ------------------------------------------------ + +local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) + +-- ------------------------------------------------ +-- Module +-- ------------------------------------------------ + +local GridHelper = {} + +function GridHelper.getScreenGridDimensions() + local tw, th = TexturePacks.getTileDimensions() + local sw, sh = love.graphics.getDimensions() + return math.floor( sw / tw ), math.floor( sh / th ) +end + +--- +-- Calculates the coordinates to be used to center an UI element of the +-- given dimensions. +-- @tparam number w The grid width of the UI element to center. +-- @tparam number h The grid height of the UI element to center. +-- @treturn number The coordinate along the x-axis. +-- @treturn number The coordinate along the y-axis. +-- +function GridHelper.centerElement( w, h ) + local sw, sh = GridHelper.getScreenGridDimensions() + return math.floor(( sw-w ) * 0.5 ), math.floor(( sh-h ) * 0.5 ) +end + +function GridHelper.pixelsToGrid( x, y ) + local tw, th = TexturePacks.getTileDimensions() + return math.floor( x / tw ), math.floor( y / th ) +end + +return GridHelper diff --git a/src/util/Log.lua b/src/util/Log.lua index 5443fa25..c6825a61 100644 --- a/src/util/Log.lua +++ b/src/util/Log.lua @@ -1,89 +1,149 @@ -local Log = {}; +--- +-- The log module handles logging of any system events to both the console and +-- a log file on the hard disk. +-- +-- If the debugging flag is set to active it will also log DEBUG events. +-- +-- @module Log +-- + +-- ------------------------------------------------ +-- Required Modules +-- ------------------------------------------------ + +local Log = {} -- ------------------------------------------------ -- Constants -- ------------------------------------------------ -local FILE_NAME = 'latest.log'; +local FILE_NAME = 'latest.log' -local DEBUG_PREFIX = '[DEBUG]'; -local WARNING_PREFIX = '[WARNING]'; -local ERROR_PREFIX = '[ERROR]'; +local DEBUG_PREFIX = '[DEBUG]' +local WARNING_PREFIX = '[WARNING]' +local ERROR_PREFIX = '[ERROR]' -- ------------------------------------------------ --- Local Variables +-- Private Variables -- ------------------------------------------------ -local file; -local active = false; +local file +local active = false -- DEBUG flag -- ------------------------------------------------ --- Local Functions +-- Private Functions -- ------------------------------------------------ +--- +-- Recreates the log file in the filesystem. +-- local function recreateFile() - file = love.filesystem.remove( FILE_NAME ); - file = love.filesystem.newFile( FILE_NAME, 'a' ); + file = love.filesystem.remove( FILE_NAME ) + file = love.filesystem.newFile( FILE_NAME, 'a' ) end +--- +-- Appends a linebreak to the log file. +-- local function appendlineBreak() - file:write( '\n' ); + file:write( '\n' ) end +--- +-- Writes a message to log file and the console. +-- +-- Logged messages follow a common layout: +-- [MESSAGE_TYPE][CALLER] Message text. +-- +-- @tparam string str The message to log. +-- @tparam string caller The module which sent the message. +-- @tparam string mtype The type of the message to log. +-- local function write( str, caller, mtype ) - str = tostring( str ); + str = tostring( str ) - local c, t = caller and string.format( '[%s]', caller ) or '', mtype or ''; - file:write( t ); - file:write( c ); + local c, t = caller and string.format( '[%s]', caller ) or '', mtype or '' + file:write( t ) + file:write( c ) - file:write( ' ' ); + file:write( ' ' ) if type( str ) ~= 'string' then - file:write( 'Can\'t log ' .. type( str )); - return; + file:write( 'Can\'t log ' .. type( str )) + return end - file:write( str ); + file:write( str ) - print( string.format( '%s%s %s', t, c, str )); + print( string.format( '%s%s %s', t, c, str )) end -- ------------------------------------------------ -- Public Functions -- ------------------------------------------------ +--- +-- Initialises the Log module. +-- function Log.init() - recreateFile(); + recreateFile() end +--- +-- Logs a message without prepending any special message type. +-- @tparam string str The message to log. +-- @tparam string caller The module which sent the message. +-- function Log.print( str, caller ) - write( str, caller, '' ); - appendlineBreak(); + write( str, caller, '' ) + appendlineBreak() end +--- +-- Logs a warning message. +-- @tparam string str The message to log. +-- @tparam string caller The module which sent the message. +-- function Log.warn( str, caller ) - write( str, caller, WARNING_PREFIX ); - appendlineBreak(); + write( str, caller, WARNING_PREFIX ) + appendlineBreak() end +--- +-- Logs an error message. +-- @tparam string str The message to log. +-- @tparam string caller The module which sent the message. +-- function Log.error( str, caller ) - write( str, caller, ERROR_PREFIX ); - appendlineBreak(); + write( str, caller, ERROR_PREFIX ) + appendlineBreak() end +--- +-- Logs a debug message. +-- @tparam string str The message to log. +-- @tparam string caller The module which sent the message. +-- function Log.debug( str, caller ) if not active then - return; + return end - write( str, caller, DEBUG_PREFIX ); - appendlineBreak(); + write( str, caller, DEBUG_PREFIX ) + appendlineBreak() end +--- +-- Activates or deactivates the debug logging. +-- @tparam boolean nactive The new state. +-- function Log.setDebugActive( nactive ) - active = nactive; + active = nactive end +--- +-- Returns wether debug logging is active or not. +-- @treturn boolean The current state. +-- function Log.getDebugActive() - return active; + return active end -return Log; +return Log diff --git a/src/util/Util.lua b/src/util/Util.lua index b7769dd1..ab13677c 100644 --- a/src/util/Util.lua +++ b/src/util/Util.lua @@ -29,4 +29,8 @@ function Util.getTilesInCircle( map, centerTile, radius ) return list; end +function Util.swap( a, b ) + return b, a +end + return Util; diff --git a/version.lua b/version.lua index f60060de..d34cb7a4 100644 --- a/version.lua +++ b/version.lua @@ -1,8 +1,8 @@ local version = { major = 0, - minor = 9, - patch = 2, - build = 1079, + minor = 10, + patch = 0, + build = 1089, } return string.format( "%d.%d.%d.%d", version.major, version.minor, version.patch, version.build );