diff --git a/CHANGELOG.md b/CHANGELOG.md index e2302cd3..a2193d21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# Version 0.12.1.1218 - 2017-11-29 + +## Fixes +- Fixed reload action taking non-ammunition items to fill magazines + + + + # Version 0.12.0.1207 - 2017-11-24 ## Additions diff --git a/README.md b/README.md index 2a7a0f12..0bf79bac 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # On The Roadside -[![Version](https://img.shields.io/badge/Version-0.12.0.1207-blue.svg)](https://github.com/rm-code/on-the-roadside/releases/latest) +[![Version](https://img.shields.io/badge/Version-0.12.1.1218-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/config.ld b/config.ld index e3a4c19b..d0aeb7d0 100644 --- a/config.ld +++ b/config.ld @@ -8,7 +8,7 @@ file = { exclude = { 'lib', 'res', - 'spc' + 'tests' } } dir = '../docs' diff --git a/src/CombatState.lua b/src/CombatState.lua index b4e0d5be..b92650b0 100644 --- a/src/CombatState.lua +++ b/src/CombatState.lua @@ -181,6 +181,10 @@ function CombatState.new() return stateManager:getState() end + function self:getPlayerFaction() + return factions:getPlayerFaction() + end + function self:getCurrentCharacter() return factions:getFaction():getCurrentCharacter(); end diff --git a/src/SaveHandler.lua b/src/SaveHandler.lua index 0543694e..e1c212d6 100644 --- a/src/SaveHandler.lua +++ b/src/SaveHandler.lua @@ -1,4 +1,13 @@ +--- +-- @module SaveHandler +-- + +-- ------------------------------------------------ +-- Required Modules +-- ------------------------------------------------ + local Log = require( 'src.util.Log' ); +local Compressor = require( 'src.util.Compressor' ) -- ------------------------------------------------ -- Module @@ -11,79 +20,20 @@ local SaveHandler = {}; -- ------------------------------------------------ local SAVE_FOLDER = 'saves' -local UNCOMPRESSED_SAVE = 'uncompressed.lua' local COMPRESSED_SAVE = 'compressed.data' local VERSION_FILE = 'version.data' -local DEBUG = false -- ------------------------------------------------ -- Private Functions -- ------------------------------------------------ --- --- Takes a table and recursively turns it into a human-readable and nicely --- formatted string stored as a sequence. --- @param value (mixed) The value to serialize. --- @param output (table) The table used for storing the lines of the final file. --- @param depth (number) An indicator for the depth of the recursion. --- -local function serialize( value, output, depth ) - -- Append whitespace for each depth layer. - local ws = ' '; - for _ = 1, depth do - ws = ws .. ' '; - end - - if type( value ) == 'table' then - for k, v in pairs(value) do - if type( v ) == 'table' then - table.insert( output, string.format( '%s[\'%s\'] = {', ws, tostring( k ))); - serialize( v, output, depth + 1 ); - table.insert( output, string.format( '%s},', ws )); - elseif type( v ) == 'string' then - table.insert( output, string.format( '%s[\'%s\'] = "%s",', ws, tostring( k ), tostring( v ))); - else - table.insert( output, string.format( '%s[\'%s\'] = %s,', ws, tostring( k ), tostring( v ))); - end - end - else - table.insert( output, string.format( '%s%s,', tostring( value ))); - end -end - ---- --- Takes care of transforming strings to numbers if possible. --- @param value (mixed) The value to check. --- @return (mixed) The converted value. +-- Creates a file containing only the version string. +-- @string dir The directory to store the version file in. +-- @table version A table containing the version field. -- -local function convertStrings( value ) - local keysToReplace = {}; - - for k, v in pairs( value ) do - if tonumber( k ) then - keysToReplace[#keysToReplace + 1] = k; - end - - if type( v ) == 'table' then - convertStrings( v ); - elseif tonumber( v ) then - value[k] = tonumber( v ); - end - end - - -- If the key can be transformed into a number delete the original - -- key-value pair and store the value with the numerical key. - for _, k in ipairs( keysToReplace ) do - local v = value[k]; - value[k] = nil; - value[tonumber(k)] = v; - end - - return value; -end - local function createVersionFile( dir, version ) - love.filesystem.write( dir .. '/' .. VERSION_FILE, love.math.compress( version, 'lz4', 9 )) + Compressor.save( version, dir .. '/' .. VERSION_FILE ) end -- ------------------------------------------------ @@ -102,38 +52,18 @@ function SaveHandler.save( t, name ) local folder = SAVE_FOLDER .. '/' .. name love.filesystem.createDirectory( folder ) - createVersionFile( folder, getVersion() ) - - -- Serialize the table. - local output = {}; - table.insert( output, 'return {' ); - serialize( t, output, 0 ) - table.insert( output, '}' ); - - local str = table.concat( output, '\n' ); - local compress = love.math.compress( str, 'lz4', 9 ); - - -- Save uncompressed output for debug purposes only. - if DEBUG then - love.filesystem.write( folder .. '/' .. UNCOMPRESSED_SAVE, str ) - end + createVersionFile( folder, { version = getVersion() }) -- Save compressed file. - love.filesystem.write( folder .. '/' .. COMPRESSED_SAVE, compress ) + Compressor.save( t, folder .. '/' .. COMPRESSED_SAVE ) end function SaveHandler.load( path ) - local compressed, bytes = love.filesystem.read( path .. '/' .. COMPRESSED_SAVE ) - Log.print( string.format( 'Loaded savegame (Size: %d bytes)', bytes ), 'SaveHandler' ); - - local decompressed = love.math.decompress( compressed, 'lz4' ); - local rawsave = loadstring( decompressed )(); - return convertStrings( rawsave ); + return Compressor.load( path .. '/' .. COMPRESSED_SAVE ) end function SaveHandler.loadVersion( path ) - local compressed = love.filesystem.read( path .. '/' .. VERSION_FILE ) - return love.math.decompress( compressed, 'lz4' ) + return Compressor.load( path .. '/' .. VERSION_FILE ).version end function SaveHandler.getSaveFolder() diff --git a/src/characters/actions/Reload.lua b/src/characters/actions/Reload.lua index 97d03e2d..34984dad 100644 --- a/src/characters/actions/Reload.lua +++ b/src/characters/actions/Reload.lua @@ -7,8 +7,10 @@ function Reload.new( character ) local self = Action.new( 5, character:getTile() ):addInstance( 'Reload' ); local function reload( weapon, inventory, item ) - weapon:getMagazine():addRound( item ); - inventory:removeItem( item ); + if item:instanceOf( 'Ammunition' ) and item:getCaliber() == weapon:getMagazine():getCaliber() then + weapon:getMagazine():addRound( item ) + inventory:removeItem( item ) + end end function self:perform() @@ -26,15 +28,18 @@ function Reload.new( character ) local inventory = character:getInventory(); for _, item in pairs( inventory:getItems() ) do - if item:instanceOf( 'Ammunition' ) and item:getCaliber() == weapon:getMagazine():getCaliber() then - reload( weapon, inventory, item ); - elseif item:instanceOf( 'ItemStack' ) then + if item:instanceOf( 'ItemStack' ) then for _, sitem in pairs( item:getItems() ) do - reload( weapon, inventory, sitem ); + reload( weapon, inventory, sitem ) if weapon:getMagazine():isFull() then - break; + return true end end + elseif item:instanceOf( 'Item' ) then + reload( weapon, inventory, item ) + if weapon:getMagazine():isFull() then + return true + end end end diff --git a/src/characters/body/BodyFactory.lua b/src/characters/body/BodyFactory.lua index 5b66ee40..5714f24f 100644 --- a/src/characters/body/BodyFactory.lua +++ b/src/characters/body/BodyFactory.lua @@ -119,21 +119,22 @@ end --- -- Assembles a body from the different body parts and connections found in the -- body template. --- @param cid (string) The body id of the creature to create. --- @param layout (table) A table containing the nodes and edges of the body graph. --- @return (Body) A shiny new Body. +-- @tparam string creatureID The body id of the creature to create. +-- @tparam table template A table containing the definitions for this creature's body parts. +-- @tparam table layout A table containing the nodes and edges of the body layout's graph. +-- @treturn Body A shiny new Body. -- -local function assembleBody( cid, layout ) - local body = Body.new( templates[cid] ); +local function assembleBody( creatureID, template, layout ) + local body = Body.new( template ) local equipment = Equipment.new(); - local inventory = Inventory.new( templates[cid].defaultCarryWeight, templates[cid].defaultCarryVolume ); + local inventory = Inventory.new( template.defaultCarryWeight, template.defaultCarryVolume ) equipment:observe( inventory ); -- The index is the number used inside of the graph whereas the id determines -- which type of object to create for this node. for index, id in ipairs( layout.nodes ) do - createBodyPart( cid, body, equipment, index, id ); + createBodyPart( creatureID, body, equipment, index, id ) end -- Connect the bodyparts. @@ -168,9 +169,12 @@ end -- @return (Body) The newly created Body. -- function BodyFactory.create( id ) - local template = layouts[id]; - assert( template, string.format( 'Requested body template (%s) doesn\'t exist!', id )); - return assembleBody( id, template ); + local layout, template = layouts[id], templates[id] + + assert( layout, string.format( 'Requested body layout (%s) doesn\'t exist!', id )) + assert( template, string.format( 'Requested body template (%s) doesn\'t exist!', id )) + + return assembleBody( id, template, layout ) end function BodyFactory.load( savedbody ) diff --git a/src/items/ItemFactory.lua b/src/items/ItemFactory.lua index ee7a94f9..4643a1ea 100644 --- a/src/items/ItemFactory.lua +++ b/src/items/ItemFactory.lua @@ -1,10 +1,18 @@ -local Log = require( 'src.util.Log' ); +--- +-- @module ItemFactory +-- + +-- ------------------------------------------------ +-- Required Modules +-- ------------------------------------------------ + +local Log = require( 'src.util.Log' ) -- ------------------------------------------------ -- Module -- ------------------------------------------------ -local ItemFactory = {}; +local ItemFactory = {} -- ------------------------------------------------ -- Constants @@ -13,30 +21,30 @@ local ItemFactory = {}; local ITEM_TYPES = require('src.constants.ITEM_TYPES') local WEAPON_TYPES = require( 'src.constants.WEAPON_TYPES' ) -local TEMPLATES_MELEE = 'res.data.items.weapons.Melee'; -local TEMPLATES_RANGED = 'res.data.items.weapons.Ranged'; -local TEMPLATES_THROWN = 'res.data.items.weapons.Thrown'; -local TEMPLATES_ARMOR = 'res.data.items.Armor'; -local TEMPLATES_CONTAINERS = 'res.data.items.Containers'; -local TEMPLATES_AMMO = 'res.data.items.Ammunition'; -local TEMPLATES_MISC = 'res.data.items.Miscellaneous'; +local TEMPLATES_MELEE = 'res.data.items.weapons.Melee' +local TEMPLATES_RANGED = 'res.data.items.weapons.Ranged' +local TEMPLATES_THROWN = 'res.data.items.weapons.Thrown' +local TEMPLATES_ARMOR = 'res.data.items.Armor' +local TEMPLATES_CONTAINERS = 'res.data.items.Containers' +local TEMPLATES_AMMO = 'res.data.items.Ammunition' +local TEMPLATES_MISC = 'res.data.items.Miscellaneous' local ITEM_CLASSES = { - [ITEM_TYPES.ARMOR] = require( 'src.items.Armor' ), - [ITEM_TYPES.CONTAINER] = require( 'src.items.Container' ), - [ITEM_TYPES.MISC] = require( 'src.items.Item' ), - [ITEM_TYPES.AMMO] = require( 'src.items.weapons.Ammunition' ), - [WEAPON_TYPES.MELEE] = require( 'src.items.weapons.MeleeWeapon' ), - [WEAPON_TYPES.RANGED] = require( 'src.items.weapons.RangedWeapon' ), - [WEAPON_TYPES.THROWN] = require( 'src.items.weapons.ThrownWeapon' ) + [ITEM_TYPES.ARMOR ] = require( 'src.items.Armor' ), + [ITEM_TYPES.CONTAINER] = require( 'src.items.Container' ), + [ITEM_TYPES.MISC ] = require( 'src.items.Item' ), + [ITEM_TYPES.AMMO ] = require( 'src.items.weapons.Ammunition' ), + [WEAPON_TYPES.MELEE ] = require( 'src.items.weapons.MeleeWeapon' ), + [WEAPON_TYPES.RANGED ] = require( 'src.items.weapons.RangedWeapon' ), + [WEAPON_TYPES.THROWN ] = require( 'src.items.weapons.ThrownWeapon' ) } -- ------------------------------------------------ -- Private Variables -- ------------------------------------------------ -local items = {}; -local counter = 0; +local items = {} +local counter = 0 -- ------------------------------------------------ -- Private Functions @@ -44,15 +52,15 @@ local counter = 0; --- -- Loads item templates from the specified directory and stores them in the items table. --- @param src (string) The module to load the templates from. +-- @tparam string src The module to load the templates from. -- local function load( src ) - local module = require( src ); + local module = require( src ) for _, template in ipairs( module ) do - items[template.id] = template; - counter = counter + 1; - Log.debug( string.format( ' %3d. %s', counter, template.id )); + items[template.id] = template + counter = counter + 1 + Log.debug( string.format( ' %3d. %s', counter, template.id )) end end @@ -64,87 +72,89 @@ end -- Loads all templates. -- function ItemFactory.loadTemplates() - Log.debug( "Load Item Templates:" ); - load( TEMPLATES_ARMOR ); - load( TEMPLATES_MELEE ); - load( TEMPLATES_RANGED ); - load( TEMPLATES_THROWN ); - load( TEMPLATES_CONTAINERS ); - load( TEMPLATES_AMMO ); - load( TEMPLATES_MISC ); + Log.debug( "Load Item Templates:" ) + load( TEMPLATES_ARMOR ) + load( TEMPLATES_MELEE ) + load( TEMPLATES_RANGED ) + load( TEMPLATES_THROWN ) + load( TEMPLATES_CONTAINERS ) + load( TEMPLATES_AMMO ) + load( TEMPLATES_MISC ) end --- -- Creates a specific item specified by type and id. --- @param id (string) The id of the item to create. --- @return (Item) The new item. +-- @tparam string id The id of the item to create. +-- @treturn Item The new item. -- function ItemFactory.createItem( id ) - local template = items[id]; - Log.debug( template.itemType .. ', ' .. tostring(template.subType), 'ItemFactory' ); + local template = items[id] + Log.debug( template.itemType .. ', ' .. tostring(template.subType), 'ItemFactory' ) if template.itemType == ITEM_TYPES.WEAPON then - return ITEM_CLASSES[template.subType].new( template ); + return ITEM_CLASSES[template.subType].new( template ) end - return ITEM_CLASSES[template.itemType].new( template ); + return ITEM_CLASSES[template.itemType].new( template ) end --- --- +-- Loads an item that was loaded from a savegame. +-- @tparam table savedItem A table containing saved information about an item. +-- @treturn Item The loaded item. -- function ItemFactory.loadItem( savedItem ) - local item = ItemFactory.createItem( savedItem.id ); + local item = ItemFactory.createItem( savedItem.id ) -- Special case for weapons that sets the weapon's attack mode and fills -- its magazine with ammunition if it is reloadable. if item:getItemType() == ITEM_TYPES.WEAPON then - item:setAttackMode( savedItem.modeIndex ); + item:setAttackMode( savedItem.modeIndex ) if item:isReloadable() then for _, round in ipairs( savedItem.magazine.rounds ) do - item:getMagazine():addRound( ItemFactory.createItem( round.id )); + item:getMagazine():addRound( ItemFactory.createItem( round.id )) end end end - return item; + return item end --- -- Creates a random item of a certain type. --- @param type (string) The type of the item to create. --- @param subType (string) The sub type of the item to create. --- @return (Item) The new item. +-- @tparam string type The type of the item to create. +-- @tparam string subType The sub type of the item to create. +-- @treturn Item The new item. -- function ItemFactory.createRandomItem( tags, type, subType ) -- Compile a list of items from this type. - local list = {}; + local list = {} for id, template in pairs( items ) do if template.itemType == type then if not subType or template.subType == subType then if tags == 'all' then - list[#list + 1] = id; + list[#list + 1] = id else -- Check if the creature's tags allow items of this type. - local whitelisted, blacklisted; + local whitelisted, blacklisted for _, itemTag in ipairs( template.tags ) do - whitelisted, blacklisted = false, false; + whitelisted, blacklisted = false, false for _, creatureTag in ipairs( tags.whitelist ) do if itemTag == creatureTag then - whitelisted = true; + whitelisted = true end end for _, creatureTag in ipairs( tags.blacklist ) do if itemTag == creatureTag then - blacklisted = true; + blacklisted = true end end if not whitelisted or blacklisted then - break; + break end end if whitelisted and not blacklisted then - list[#list + 1] = id; + list[#list + 1] = id end end end @@ -152,8 +162,8 @@ function ItemFactory.createRandomItem( tags, type, subType ) end -- Select a random item from the list. - local id = list[love.math.random( 1, #list )]; - return ItemFactory.createItem( id ); + local id = list[love.math.random( 1, #list )] + return ItemFactory.createItem( id ) end -return ItemFactory; +return ItemFactory diff --git a/src/items/weapons/Magazine.lua b/src/items/weapons/Magazine.lua index e6c690d7..922217e4 100644 --- a/src/items/weapons/Magazine.lua +++ b/src/items/weapons/Magazine.lua @@ -1,60 +1,118 @@ -local Object = require( 'src.Object' ); - -local Magazine = {}; - -function Magazine.new( caliber, capacity ) - local self = Object.new():addInstance( 'Magazine' ); - - local rounds = {}; - - function self:addRound( nround ) - rounds[#rounds + 1] = nround; - end +--- +-- This module is used to keep track of the rounds currently loaded into a +-- reloadable weapon. +-- @module Magazine +-- + +-- ------------------------------------------------ +-- Required Modules +-- ------------------------------------------------ + +local Class = require( 'lib.Middleclass' ) + +-- ------------------------------------------------ +-- Module +-- ------------------------------------------------ + +local Magazine = Class( 'Magazine' ) + +-- ------------------------------------------------ +-- Public Methods +-- ------------------------------------------------ + +--- +-- Initializes a new instance of the Magazine class. +-- @tparam string caliber The caliber type the magazine can store. +-- @tparam number capacity The maximum amount of rounds this magazine can hold. +-- +function Magazine:initialize( caliber, capacity ) + self.caliber = caliber + self.capacity = capacity + + self.rounds = {} +end - function self:removeRound() - table.remove( rounds, 1 ); - end +--- +-- Adds a new round to the magazine. +-- @tparam Ammunition round The round to add. +-- +function Magazine:addRound( round ) + assert( round:instanceOf( 'Ammunition' ), 'Expected an item of type Ammunition!' ) + self.rounds[#self.rounds + 1] = round +end - function self:getRounds() - return #rounds; - end +--- +-- Removes a round from the magazine. +-- +function Magazine:removeRound() + table.remove( self.rounds, 1 ) +end - function self:getRound( i ) - return rounds[i]; - end +--- +-- Serializes the magazine. +-- +function Magazine:serialize() + local t = {} - function self:getCapacity() - return capacity; + t['rounds'] = {} + for i, round in ipairs( self.rounds ) do + t['rounds'][i] = round:serialize() end - function self:getCaliber() - return caliber; - end + return t +end - function self:isFull() - return #rounds == capacity; - end +-- ------------------------------------------------ +-- Getters +-- ------------------------------------------------ - function self:isEmpty() - return #rounds == 0; - end +--- +-- Gets the magazine's maximum capacity. +-- @treturn number The number of rounds the magazine can hold. +-- +function Magazine:getCapacity() + return self.capacity +end - function self:setCapacity( ncapacity ) - capacity = ncapacity; - end +--- +-- Gets the magazine's ammunition type. +-- @treturn string The type of ammunition this magazine can hold. +-- +function Magazine:getCaliber() + return self.caliber +end - function self:serialize() - local t = {}; +--- +-- Gets a certain round inside of the magazine. +-- @tparam number i The index of the round to get. +-- @treturn Ammunition The round at the specified position. +-- +function Magazine:getRound( i ) + return self.rounds[i] +end - t['rounds'] = {} - for i, round in ipairs( rounds ) do - t['rounds'][i] = round:serialize(); - end +--- +-- Gets the current number of rounds. +-- @treturn number The current amount of rounds inside of the mag. +-- +function Magazine:getNumberOfRounds() + return #self.rounds +end - return t; - end +--- +-- Checks if the magazine is at full capacity. +-- @treturn boolean True if the magazine is full. +-- +function Magazine:isFull() + return #self.rounds == self.capacity +end - return self; +--- +-- Checks if the magazine is empty. +-- @treturn boolean True if the magazine is empty. +-- +function Magazine:isEmpty() + return #self.rounds == 0 end -return Magazine; +return Magazine diff --git a/src/items/weapons/ProjectileQueue.lua b/src/items/weapons/ProjectileQueue.lua index 8eaab05a..42b96156 100644 --- a/src/items/weapons/ProjectileQueue.lua +++ b/src/items/weapons/ProjectileQueue.lua @@ -75,7 +75,7 @@ function ProjectileQueue.new( character, tx, ty, th ) -- the queue. -- function self:init() - shots = math.min( weapon:getMagazine():getRounds(), weapon:getAttacks() ); + shots = math.min( weapon:getMagazine():getNumberOfRounds(), weapon:getAttacks() ); for i = 1, shots do ammoQueue:enqueue( weapon:getMagazine():getRound( i )); end diff --git a/src/items/weapons/RangedWeapon.lua b/src/items/weapons/RangedWeapon.lua index 2f540550..625a5ff6 100644 --- a/src/items/weapons/RangedWeapon.lua +++ b/src/items/weapons/RangedWeapon.lua @@ -12,7 +12,7 @@ function RangedWeapon.new( template ) local rpm = template.rpm or 60; local firingDelay = 1 / ( rpm / 60 ); - local magazine = Magazine.new( template.caliber, template.magSize ); + local magazine = Magazine( template.caliber, template.magSize ) local range = template.range; -- ------------------------------------------------ diff --git a/src/ui/MapPainter.lua b/src/ui/MapPainter.lua index f0b34fb4..68f20a87 100644 --- a/src/ui/MapPainter.lua +++ b/src/ui/MapPainter.lua @@ -12,6 +12,7 @@ -- Required Modules -- ------------------------------------------------ +local Class = require( 'lib.Middleclass' ) local Log = require( 'src.util.Log' ) local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) @@ -19,7 +20,7 @@ local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) -- Module -- ------------------------------------------------ -local MapPainter = {} +local MapPainter = Class( 'MapPainter' ) -- ------------------------------------------------ -- Constants @@ -44,51 +45,35 @@ local COLORS = { } -- ------------------------------------------------ --- Constructor +-- Private Methods -- ------------------------------------------------ --- --- Generates a new instance of the MapPainter class. +-- Adds an empty sprite for each tile in the map to the spritebatch, gives +-- each tile a unique identifier and sets it to dirty for the first update. +-- @tparam Map map The map to draw. +-- @tparam SpriteBatch spritebatch The spritebatch to initialize. -- -function MapPainter.new() - local self = {} - - -- ------------------------------------------------ - -- Private Attributes - -- ------------------------------------------------ - - local spritebatch - local tileset - local tw, th - - local map, factions - - -- ------------------------------------------------ - -- Private Methods - -- ------------------------------------------------ - - --- - -- Adds an empty sprite for each tile in the map to the spritebatch, gives - -- each tile a unique identifier and sets it to dirty for the first update. - -- - local function initSpritebatch() - map:iterate( function( tile, x, y ) - local id = spritebatch:add( tileset:getSprite( 'tile_empty' ), x * tw, y * th ) - tile:setSpriteID( id ) - tile:setDirty( true ) - end) - Log.debug( string.format('Initialised %d tiles.', spritebatch:getCount()), 'MapPainter' ) - end - - --- - -- 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. - -- @treturn table A table containing RGBA values. - -- - local function selectTileColor( tile, faction ) - local character = faction:getCurrentCharacter() +local function initSpritebatch( map, spritebatch ) + local tw, th = TexturePacks.getTileDimensions() + map:iterate( function( tile, x, y ) + local id = spritebatch:add( TexturePacks.getSprite( 'tile_empty' ), x * tw, y * th ) + tile:setSpriteID( id ) + tile:setDirty( true ) + end) + Log.debug( string.format( 'Initialised %d tiles.', spritebatch:getCount() ), 'MapPainter' ) +end +--- +-- 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. +-- @treturn table A table containing RGBA values. +-- +local function selectTileColor( tile, faction ) + -- If there is a faction we check which tiles are explored and which tiles + -- are currently seen. + if faction then -- Hide unexplored tiles. if not tile:isExplored( faction:getType() ) then return TexturePacks.getColor( 'tile_unexplored' ) @@ -99,117 +84,130 @@ function MapPainter.new() return TexturePacks.getColor( 'tile_unseen' ) end - if tile:isOccupied() then - local tchar = tile:getCharacter() - if tchar == character then - return TexturePacks.getColor( COLORS[tchar:getFaction():getType()].ACTIVE ) - end - return TexturePacks.getColor( COLORS[tchar:getFaction():getType()].INACTIVE ) - end - - if not tile:getInventory():isEmpty() then - return TexturePacks.getColor( 'items' ) - end - - if tile:hasWorldObject() then - return TexturePacks.getColor( tile:getWorldObject():getID() ) + -- Highlight activate character. + if tile:getCharacter() == faction:getCurrentCharacter() then + return TexturePacks.getColor( COLORS[tile:getCharacter():getFaction():getType()].ACTIVE ) end + end - return TexturePacks.getColor( tile:getID() ) + if tile:isOccupied() then + return TexturePacks.getColor( COLORS[tile:getCharacter():getFaction():getType()].INACTIVE ) end - --- - -- Selects the tile for drawing a tile occupied by a character. - -- @tparam Tile tile The tile to pick a sprite for. - -- @treturn Quad A quad pointing to the sprite on the active tileset. - -- - local function selectCharacterTile( tile ) - local character = tile:getCharacter() - return tileset:getSprite( character:getBody():getID(), character:getStance() ) + if not tile:getInventory():isEmpty() then + return TexturePacks.getColor( 'items' ) end - local function selectWorldObjectSprite( worldObject ) - if worldObject:isOpenable() then - if worldObject:isPassable() then - return tileset:getSprite( worldObject:getID(), 'open' ) - else - return tileset:getSprite( worldObject:getID(), 'closed' ) - end - end - return tileset:getSprite( worldObject:getID() ) + if tile:hasWorldObject() then + return TexturePacks.getColor( tile:getWorldObject():getID() ) end - --- - -- Selects a sprite from the tileset based on the tile and its contents. - -- @tparam Tile tile The tile to choose a sprite for. - -- @tparam Faction faction The faction to draw for. - -- @treturn Quad A quad pointing to a sprite on the tileset. - -- - local function selectTileSprite( tile, faction ) - if tile:isOccupied() and faction:canSee( tile ) then - return selectCharacterTile( tile ) - end + return TexturePacks.getColor( tile:getID() ) +end - if not tile:getInventory():isEmpty() then - return tileset:getSprite( 'items' ) - end +--- +-- Selects the tile for drawing a tile occupied by a character. +-- @tparam Tile tile The tile to pick a sprite for. +-- @treturn Quad A quad pointing to the sprite on the active tileset. +-- +local function selectCharacterTile( tile ) + local character = tile:getCharacter() + return TexturePacks.getSprite( character:getBody():getID(), character:getStance() ) +end - if tile:hasWorldObject() then - return selectWorldObjectSprite( tile:getWorldObject() ) +--- +-- Selects the tile to use for drawing a worldobject. +-- @tparam WorldObject worldObject The worldobject to pick a sprite for. +-- @treturn Quad A quad pointing to the sprite on the active tileset. +-- +local function selectWorldObjectSprite( worldObject ) + if worldObject:isOpenable() then + if worldObject:isPassable() then + return TexturePacks.getSprite( worldObject:getID(), 'open' ) + else + return TexturePacks.getSprite( worldObject:getID(), 'closed' ) end - - return tileset:getSprite( tile:getID() ) end + return TexturePacks.getSprite( worldObject:getID() ) +end - --- - -- Updates the spritebatch by going through every tile in the map. Only - -- tiles which have been marked as dirty will be sent to the spritebatch. - -- - local function updateSpritebatch() - local faction = factions:getPlayerFaction() - map:iterate( function( tile, x, y ) - if tile:isDirty() then - spritebatch:setColor( selectTileColor( tile, faction )) - spritebatch:set( tile:getSpriteID(), selectTileSprite( tile, faction ), x * tw, y * th ) - tile:setDirty( false ) - end - end) +--- +-- Selects a sprite from the tileset based on the tile and its contents. +-- @tparam Tile tile The tile to choose a sprite for. +-- @tparam Faction faction The faction to draw for. +-- @treturn Quad A quad pointing to a sprite on the tileset. +-- +local function selectTileSprite( tile, faction ) + if tile:isOccupied() and faction and faction:canSee( tile ) then + return selectCharacterTile( tile ) end - -- ------------------------------------------------ - -- Public Methods - -- ------------------------------------------------ - - --- - -- Initialises the MapPainter. - -- @tparam Map nmap The map to draw. - -- @tparam Factions nfactions The factions handler. - -- - function self:init( nmap, nfactions ) - map, factions = nmap, nfactions - - tileset = TexturePacks.getTileset() - tw, th = tileset:getTileDimensions() - TexturePacks.setBackgroundColor() - spritebatch = love.graphics.newSpriteBatch( tileset:getSpritesheet(), MAX_SPRITES, 'dynamic' ) - initSpritebatch() + if not tile:getInventory():isEmpty() then + return TexturePacks.getSprite( 'items' ) end - --- - -- Draws the game's world. - -- - function self:draw() - love.graphics.draw( spritebatch, 0, 0 ) + if tile:hasWorldObject() then + return selectWorldObjectSprite( tile:getWorldObject() ) end - --- - -- Updates the spritebatch for the game's world. - -- - function self:update() - updateSpritebatch() - end + return TexturePacks.getSprite( tile:getID() ) +end + +--- +-- Updates the spritebatch by going through every tile in the map. Only +-- tiles which have been marked as dirty will be sent to the spritebatch. +-- @tparam SpriteBatch spritebatch The spritebatch to update. +-- @tparam Map map The map to draw. +-- @tparam Faction faction The player's faction. +-- +local function updateSpritebatch( spritebatch, map, faction ) + local tw, th = TexturePacks.getTileDimensions() + map:iterate( function( tile, x, y ) + if tile:isDirty() then + spritebatch:setColor( selectTileColor( tile, faction )) + spritebatch:set( tile:getSpriteID(), selectTileSprite( tile, faction ), x * tw, y * th ) + tile:setDirty( false ) + end + end) +end + +-- ------------------------------------------------ +-- Public Methods +-- ------------------------------------------------ + +--- +-- Initializes the new instance of the MapPainter class. +-- @tparam Map map The map to draw. +-- +function MapPainter:initialize( map ) + self.map = map - return self + TexturePacks.setBackgroundColor() + self.spritebatch = love.graphics.newSpriteBatch( TexturePacks.getTileset():getSpritesheet(), MAX_SPRITES, 'dynamic' ) + initSpritebatch( self.map, self.spritebatch ) +end + +--- +-- Draws the game's world. +-- +function MapPainter:draw() + love.graphics.draw( self.spritebatch, 0, 0 ) +end + +--- +-- Updates the spritebatch for the game's world. +-- +function MapPainter:update() + updateSpritebatch( self.spritebatch, self.map, self.faction ) +end + +--- +-- Sets the faction which is used for checking which parts of the map are visible +-- and explored. +-- @tparam Faction faction The faction to use. +-- +function MapPainter:setActiveFaction( faction ) + self.faction = faction end return MapPainter diff --git a/src/ui/ProcMapPainter.lua b/src/ui/ProcMapPainter.lua deleted file mode 100644 index 98442b41..00000000 --- a/src/ui/ProcMapPainter.lua +++ /dev/null @@ -1,158 +0,0 @@ ---- --- This module takes care of drawing the game's map tiles. --- It uses a spritebatch to optimise the drawing operation since the map --- remains static and only needs to be updated when the state of the world --- changes. --- Only tiles which are marked as dirty will be updated by the MapPainter. --- --- @module MapPainter --- - --- ------------------------------------------------ --- Required Modules --- ------------------------------------------------ - -local Log = require( 'src.util.Log' ) -local TexturePacks = require( 'src.ui.texturepacks.TexturePacks' ) - --- ------------------------------------------------ --- Module --- ------------------------------------------------ - -local MapPainter = {} - --- ------------------------------------------------ --- Constructor --- ------------------------------------------------ - ---- --- Generates a new instance of the MapPainter class. --- -function MapPainter.new() - local self = {} - - -- ------------------------------------------------ - -- Private Attributes - -- ------------------------------------------------ - - local spritebatch - local tileset - local tw, th - - local map - - -- ------------------------------------------------ - -- Private Methods - -- ------------------------------------------------ - - --- - -- Adds an empty sprite for each tile in the map to the spritebatch, gives - -- each tile a unique identifier and sets it to dirty for the first update. - -- - local function initSpritebatch() - map:iterate( function( tile, x, y ) - local id = spritebatch:add( tileset:getSprite( 'tile_empty' ), x * tw, y * th ) - tile:setSpriteID( id ) - tile:setDirty( true ) - end) - Log.debug( string.format('Initialised %d tiles.', spritebatch:getCount()), 'MapPainter' ) - end - - --- - -- 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 ) - if not tile:getInventory():isEmpty() then - return TexturePacks.getColor( 'items' ) - end - - if tile:hasWorldObject() then - return TexturePacks.getColor( tile:getWorldObject():getID() ) - end - - return TexturePacks.getColor( tile:getID() ) - end - - local function selectWorldObjectSprite( worldObject ) - if worldObject:isOpenable() then - if worldObject:isPassable() then - return tileset:getSprite( worldObject:getID(), 'open' ) - else - return tileset:getSprite( worldObject:getID(), 'closed' ) - end - end - return tileset:getSprite( worldObject:getID() ) - end - - --- - -- Selects a sprite from the tileset based on the tile and its contents. - -- @tparam Tile tile The tile to choose a sprite for. - -- @tparam Faction faction The faction to draw for. - -- @treturn Quad A quad pointing to a sprite on the tileset. - -- - local function selectTileSprite( tile ) - if not tile:getInventory():isEmpty() then - return tileset:getSprite( 'items' ) - end - - if tile:hasWorldObject() then - return selectWorldObjectSprite( tile:getWorldObject() ) - end - - return tileset:getSprite( tile:getID() ) - end - - --- - -- Updates the spritebatch by going through every tile in the map. Only - -- tiles which have been marked as dirty will be sent to the spritebatch. - -- - local function updateSpritebatch() - map:iterate( function( tile, x, y ) - if tile:isDirty() then - spritebatch:setColor( selectTileColor( tile, nil, nil )) - spritebatch:set( tile:getSpriteID(), selectTileSprite( tile, nil ), x * tw, y * th ) - tile:setDirty( false ) - end - end) - end - - -- ------------------------------------------------ - -- Public Methods - -- ------------------------------------------------ - - --- - -- Initialises the MapPainter. - -- @tparam Map nmap The map to draw. - -- - function self:init( nmap ) - map = nmap - - tileset = TexturePacks.getTileset() - tw, th = tileset:getTileDimensions() - TexturePacks.setBackgroundColor() - spritebatch = love.graphics.newSpriteBatch( tileset:getSpritesheet(), 30000, 'dynamic' ) - initSpritebatch() - end - - --- - -- Draws the game's world. - -- - function self:draw() - love.graphics.draw( spritebatch, 0, 0 ) - end - - --- - -- Updates the spritebatch for the game's world. - -- - function self:update() - updateSpritebatch() - end - - return self -end - -return MapPainter diff --git a/src/ui/UserInterface.lua b/src/ui/UserInterface.lua index 98eabaa6..65102ba4 100644 --- a/src/ui/UserInterface.lua +++ b/src/ui/UserInterface.lua @@ -69,7 +69,7 @@ function UserInterface.new( game, camera ) 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 ) + local text = string.format( ' %d/%d (%d)', magazine:getNumberOfRounds(), 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 diff --git a/src/ui/screens/CombatScreen.lua b/src/ui/screens/CombatScreen.lua index 8a0e0cdf..c2447e4b 100644 --- a/src/ui/screens/CombatScreen.lua +++ b/src/ui/screens/CombatScreen.lua @@ -39,8 +39,7 @@ function CombatScreen.new() combatState = CombatState.new() combatState:init( playerFaction, savegame ) - mapPainter = MapPainter.new() - mapPainter:init( combatState:getMap(), combatState:getFactions() ) + mapPainter = MapPainter( combatState:getMap() ) local mw, mh = combatState:getMap():getDimensions() camera = CameraHandler.new( mw, mh, tw, th ) @@ -64,6 +63,7 @@ function CombatScreen.new() end combatState:update( dt ) + mapPainter:setActiveFaction( combatState:getPlayerFaction() ) mapPainter:update( dt ) overlayPainter:update( dt ) userInterface:update( dt ) diff --git a/src/ui/screens/MapTest.lua b/src/ui/screens/MapTest.lua index 7372eeae..0b554661 100644 --- a/src/ui/screens/MapTest.lua +++ b/src/ui/screens/MapTest.lua @@ -1,6 +1,6 @@ local Screen = require( 'lib.screenmanager.Screen' ) local ScreenManager = require( 'lib.screenmanager.ScreenManager' ) -local MapPainter = require( 'src.ui.ProcMapPainter' ) +local MapPainter = require( 'src.ui.MapPainter' ) local CameraHandler = require('src.ui.CameraHandler') local PrefabLoader = require( 'src.map.procedural.PrefabLoader' ) local ProceduralMapGenerator = require( 'src.map.procedural.ProceduralMapGenerator' ) @@ -45,8 +45,7 @@ function MapTest.new() createMap() - mapPainter = MapPainter.new() - mapPainter:init( map ) + mapPainter = MapPainter( map ) camera = CameraHandler.new( mw, mh, TexturePacks.getTileDimensions() ) end diff --git a/tests/spc/tile_template_spec.lua b/tests/spc/tile_template_spec.lua new file mode 100644 index 00000000..69aba55c --- /dev/null +++ b/tests/spc/tile_template_spec.lua @@ -0,0 +1,34 @@ +describe( 'Tile template spec', function() + local FILE_PATH = 'res.data.Tiles' + + local templates + + setup( function() + templates = require( FILE_PATH ) + end) + + it( 'makes sure all tile templates have and id', function() + for i, template in ipairs( templates ) do + assert.is.truthy( template.id, string.format( "Index number %d.", i )) + end + end) + + it( 'makes sure all passable tiles have movement costs', function() + for _, template in ipairs( templates ) do + if template.passable then + assert.is.truthy( template.movementCost, template.id ) + assert.is.truthy( template.movementCost.stand, template.id ) + assert.is.truthy( template.movementCost.crouch, template.id ) + assert.is.truthy( template.movementCost.prone, template.id ) + end + end + end) + + it( 'makes sure all impassable tiles have no movement costs', function() + for _, template in ipairs( templates ) do + if not template.passable then + assert.is.falsy( template.movementCost, template.id ) + end + end + end) +end) diff --git a/version.lua b/version.lua index f6104f62..611f83b4 100644 --- a/version.lua +++ b/version.lua @@ -1,8 +1,8 @@ local version = { major = 0, minor = 12, - patch = 0, - build = 1207, + patch = 1, + build = 1218, } return string.format( "%d.%d.%d.%d", version.major, version.minor, version.patch, version.build );