diff --git a/CHANGELOG.md b/CHANGELOG.md index f90b1524..b7aa914f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,19 @@ +# Version 0.16.1.1627 - 2018-09-02 (LÖVE 11.1) + +## Additions +- Added support for latin-1 charset. + +## Fixes +- Fixed camera focusing on the wrong character when switching from the current to the previous character. +- Fixed faulty loading of world objects. +- Fixed faulty loading of characters. + +## Other Changes +- Changed how fonts are loaded by the game to support different charsets. + + + + # Version 0.16.0.1615 - 2018-08-30 (LÖVE 11.1) ## Additions diff --git a/README.md b/README.md index e023223b..04a4bbc4 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # On The Roadside -[![Version](https://img.shields.io/badge/Version-0.16.0.1615-blue.svg)](https://github.com/rm-code/on-the-roadside/releases/latest) -[![LOVE](https://img.shields.io/badge/L%C3%96VE-11.0-EA316E.svg)](http://love2d.org/) +[![Version](https://img.shields.io/badge/Version-0.16.1.1627-blue.svg)](https://github.com/rm-code/on-the-roadside/releases/latest) +[![LOVE](https://img.shields.io/badge/L%C3%96VE-11.1-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) _On the Roadside_ is a turn-based strategy game in which you take control of a squad of mercenaries fighting for survival in a world shaped by unknown forces. It currently is in the very _early stages_ of development. diff --git a/res/texturepacks/default/imagefont.png b/res/texturepacks/default/imagefont.png deleted file mode 100644 index 65138eaf..00000000 Binary files a/res/texturepacks/default/imagefont.png and /dev/null differ diff --git a/res/texturepacks/default/imagefont_latin_1.png b/res/texturepacks/default/imagefont_latin_1.png new file mode 100644 index 00000000..d656ee14 Binary files /dev/null and b/res/texturepacks/default/imagefont_latin_1.png differ diff --git a/res/texturepacks/default/imagefont_latin_basic.png b/res/texturepacks/default/imagefont_latin_basic.png new file mode 100644 index 00000000..ec34d026 Binary files /dev/null and b/res/texturepacks/default/imagefont_latin_basic.png differ diff --git a/res/texturepacks/default/info.lua b/res/texturepacks/default/info.lua index 6e762e95..d2112e3f 100644 --- a/res/texturepacks/default/info.lua +++ b/res/texturepacks/default/info.lua @@ -9,11 +9,23 @@ return { } }, font = { - source = 'imagefont.png', - glyphs = { - source = ' ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyzÄÖÜäöü0123456789.,:;!?-+/()[]%&"\'*=_<>ß^©|', - width = 8, - height = 16 + width = 8, + height = 16, + charsets = { + { + -- LATIN BASIC + -- 0020-007F (Excluded: 007F) + -- !"# $%&' ()*+ ,-./ 0123 4567 89:; <=>? @ABC DEFG HIJK LMNO PQRS TUVW XYZ[ \]^_ `abc defg hijk lmno pqrs tuvw xyz{ |} + source = 'imagefont_latin_basic.png', + glyphs = [[ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~]] + }, + { + -- LATIN-1 + -- 00A0-00FF (Excluded: 00A0, 00AD) + -- ¡¢£¤ ¥¦§¨ ©ª«¬ ®¯°± ²³´µ ¶·¸¹ º»¼½ ¾¿ÀÁ ÂÃÄÅ ÆÇÈÉ ÊËÌÍ ÎÏÐÑ ÒÓÔÕ Ö×ØÙ ÚÛÜÝ Þßàá âãäå æçèé êëìí îïðñ òóôõ ö÷øù úûüý þÿ + source = 'imagefont_latin_1.png', + glyphs = [[¡¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ]] + } } } } diff --git a/src/CombatState.lua b/src/CombatState.lua index 4aa275c6..c81fc661 100644 --- a/src/CombatState.lua +++ b/src/CombatState.lua @@ -83,13 +83,15 @@ function CombatState:initialize( playerFaction, savegame ) end) end) - self.stateManager = StateManager( self.states ) - self.stateManager:push( 'planning', self.factions ) + self.explosionManager = ExplosionManager( self.map ) + + self.projectileManager = ProjectileManager( self.map ) + self.projectileManager:observe( self.explosionManager ) - self.sadisticAIDirector = SadisticAIDirector( self.factions, self.stateManager ) + self.stateManager = StateManager( self.states ) + self.stateManager:push( 'planning', self.factions, self.explosionManager, self.projectileManager ) - ProjectileManager.init( self.map ) - ExplosionManager.init( self.map ) + self.sadisticAIDirector = SadisticAIDirector( self.factions, self.stateManager, self.explosionManager, self.projectileManager ) -- Register observations. self.map:observe( self ) @@ -137,11 +139,6 @@ function CombatState:serialize() return t end -function CombatState:close() - ProjectileManager.clear() - ExplosionManager.clear() -end - function CombatState:keypressed( _, scancode, _ ) if self.factions:getFaction():isAIControlled() or self.stateManager:blocksInput() then return @@ -160,6 +157,14 @@ function CombatState:getMap() return self.map end +function CombatState:getExplosionManager() + return self.explosionManager +end + +function CombatState:getProjectileManager() + return self.projectileManager +end + function CombatState:getFactions() return self.factions end diff --git a/src/Messenger.lua b/src/Messenger.lua deleted file mode 100644 index 79b3a9ce..00000000 --- a/src/Messenger.lua +++ /dev/null @@ -1,55 +0,0 @@ ---- --- @module Messenger --- - --- ------------------------------------------------ --- Module --- ------------------------------------------------ - -local Messenger = {} - --- ------------------------------------------------ --- Local Variables --- ------------------------------------------------ - -local subscriptions = {} -local index = 0 - --- ------------------------------------------------ --- Public Functions --- ------------------------------------------------ - ---- --- Publishes a message to all subscribers. --- @tparam string message The message's type. --- @tparam vararg ... One or multiple arguments passed to the subscriber. --- -function Messenger.publish( message, ... ) - for _, subscription in pairs( subscriptions ) do - if subscription.message == message then - subscription.callback( ... ) - end - end -end - ---- --- Registers a callback belonging to a certain subscriber. --- @tparam string message The message to listen for. --- @tparam function callback The function to call once the message is published. --- @treturn number The index pointing to the subscription. --- -function Messenger.observe( message, callback ) - index = index + 1 - subscriptions[index] = { message = message, callback = callback } - return index -end - ---- --- Removes a subscription based on its index. --- @tparam number nindex The index of the subscription to remove. --- -function Messenger.remove( nindex ) - subscriptions[nindex] = nil -end - -return Messenger diff --git a/src/characters/Character.lua b/src/characters/Character.lua index d0803752..aba8eb2b 100644 --- a/src/characters/Character.lua +++ b/src/characters/Character.lua @@ -285,6 +285,7 @@ function Character:serialize() local t = { ['class'] = self.creatureClass, ['name'] = self.name, + ['maxActionPoints'] = self.maxActionPoints, ['actionPoints'] = self.actionPoints, ['accuracy'] = self.accuracy, ['throwingSkill'] = self.throwingSkill, diff --git a/src/characters/CharacterFactory.lua b/src/characters/CharacterFactory.lua index 70b0c686..71ebcb1d 100644 --- a/src/characters/CharacterFactory.lua +++ b/src/characters/CharacterFactory.lua @@ -178,7 +178,7 @@ function CharacterFactory.init() end function CharacterFactory.loadCharacter( savedCharacter ) - local character = Character( savedCharacter.class ) + local character = Character( savedCharacter.class, savedCharacter.maxActionPoints ) character:setName( savedCharacter.name ) character:setActionPoints( savedCharacter.actionPoints ) diff --git a/src/characters/Faction.lua b/src/characters/Faction.lua index 2d7d17c9..e6b91ca6 100644 --- a/src/characters/Faction.lua +++ b/src/characters/Faction.lua @@ -244,14 +244,18 @@ end -- @treturn Character The active Character. -- function Faction:prevCharacter() - local previousCharacter = self.active:getObject() + -- Get the currently active character. + local currentCharacter = self.active:getObject() while self.active do + -- Select the previous character or wrap around to the last character + -- in the list. self.active = self.active:getPrev() or self.last - local character = self.active:getObject() - if not character:isDead() then + + local previousCharacter = self.active:getObject() + if not previousCharacter:isDead() then + currentCharacter:deactivate() previousCharacter:activate() - character:deactivate() - return character + return previousCharacter end end end diff --git a/src/characters/actions/RangedAttack.lua b/src/characters/actions/RangedAttack.lua index e43767ad..9e98fece 100644 --- a/src/characters/actions/RangedAttack.lua +++ b/src/characters/actions/RangedAttack.lua @@ -7,7 +7,6 @@ -- ------------------------------------------------ local Action = require( 'src.characters.actions.Action' ) -local ProjectileManager = require( 'src.items.weapons.ProjectileManager' ) local ProjectileQueue = require( 'src.items.weapons.ProjectileQueue' ) local Bresenham = require( 'lib.Bresenham' ) @@ -21,8 +20,10 @@ local RangedAttack = Action:subclass( 'RangedAttack' ) -- Public Methods -- ------------------------------------------------ -function RangedAttack:initialize( character, target ) +function RangedAttack:initialize( character, target, projectileManager ) Action.initialize( self, character, target, character:getWeapon():getAttackCost() ) + + self.projectileManager = projectileManager end function RangedAttack:perform() @@ -45,8 +46,7 @@ function RangedAttack:perform() return true end) - local package = ProjectileQueue( self.character, ax, ay, th ) - ProjectileManager.register( package ) + self.projectileManager:register( ProjectileQueue( self.character, ax, ay, th )) return true end diff --git a/src/characters/actions/ThrowingAttack.lua b/src/characters/actions/ThrowingAttack.lua index 6cc8bca7..fc4b0b90 100644 --- a/src/characters/actions/ThrowingAttack.lua +++ b/src/characters/actions/ThrowingAttack.lua @@ -7,7 +7,6 @@ -- ------------------------------------------------ local Action = require( 'src.characters.actions.Action' ) -local ProjectileManager = require( 'src.items.weapons.ProjectileManager' ) local ThrownProjectileQueue = require( 'src.items.weapons.ThrownProjectileQueue' ) local Bresenham = require( 'lib.Bresenham' ) @@ -21,8 +20,10 @@ local ThrowingAttack = Action:subclass( 'ThrowingAttack' ) -- Public Methods -- ------------------------------------------------ -function ThrowingAttack:initialize( character, target ) +function ThrowingAttack:initialize( character, target, projectileManager ) Action.initialize( self, character, target, character:getWeapon():getAttackCost() ) + + self.projectileManager = projectileManager end function ThrowingAttack:perform() @@ -40,8 +41,7 @@ function ThrowingAttack:perform() return true end) - local package = ThrownProjectileQueue( self.character, ax, ay, th ) - ProjectileManager.register( package ) + self.projectileManager:register( ThrownProjectileQueue( self.character, ax, ay, th )) return true end diff --git a/src/characters/ai/SadisticAIDirector.lua b/src/characters/ai/SadisticAIDirector.lua index 11e1fa7c..b6a6b7ac 100644 --- a/src/characters/ai/SadisticAIDirector.lua +++ b/src/characters/ai/SadisticAIDirector.lua @@ -20,18 +20,20 @@ local SadisticAIDirector = Class( 'SadisticAIDirector' ) -- Private Methods -- ------------------------------------------------ -local function tickBehaviorTree( tree, character, states, factions ) +local function tickBehaviorTree( tree, character, states, factions, projectileManager ) Log.debug( "Tick BehaviorTree for " .. tostring( character ), 'SadisticAIDirector' ) - return tree:traverse( {}, character, states, factions ) + return tree:traverse( {}, character, states, factions, projectileManager ) end -- ------------------------------------------------ -- Public Methods -- ------------------------------------------------ -function SadisticAIDirector:initialize( factions, states ) +function SadisticAIDirector:initialize( factions, states, explosionManager, projectileManager ) self.factions = factions self.states = states + self.explosionManager = explosionManager + self.projectileManager = projectileManager end function SadisticAIDirector:update() @@ -47,9 +49,9 @@ function SadisticAIDirector:update() Log.debug( 'Select next character for this turn', 'SadisticAIDirector' ) local character = faction:nextCharacterForTurn() - local success = tickBehaviorTree( tree, character, self.states, self.factions ) + local success = tickBehaviorTree( tree, character, self.states, self.factions, self.projectileManager ) if success then - self.states:push( 'execution', self.factions, character ) + self.states:push( 'execution', self.factions, character, self.explosionManager, self.projectileManager ) return end diff --git a/src/characters/ai/behaviortree/leafs/BTAttackTarget.lua b/src/characters/ai/behaviortree/leafs/BTAttackTarget.lua index cccb2357..8c3ac52e 100644 --- a/src/characters/ai/behaviortree/leafs/BTAttackTarget.lua +++ b/src/characters/ai/behaviortree/leafs/BTAttackTarget.lua @@ -21,9 +21,9 @@ local BTAttackTarget = BTLeaf:subclass( 'BTAttackTarget' ) -- ------------------------------------------------ function BTAttackTarget:traverse( ... ) - local blackboard, character = ... + local blackboard, character, _, _, projectileManager = ... - local success = character:enqueueAction( RangedAttack( character, blackboard.target )) + local success = character:enqueueAction( RangedAttack( character, blackboard.target, projectileManager )) if success then Log.debug( 'Character attacks target', 'BTAttackTarget' ) return true diff --git a/src/characters/ai/behaviortree/leafs/BTThrowingAttack.lua b/src/characters/ai/behaviortree/leafs/BTThrowingAttack.lua index 64720e46..b86c78dd 100644 --- a/src/characters/ai/behaviortree/leafs/BTThrowingAttack.lua +++ b/src/characters/ai/behaviortree/leafs/BTThrowingAttack.lua @@ -21,9 +21,9 @@ local BTThrowingAttack = BTLeaf:subclass( 'BTThrowingAttack' ) -- ------------------------------------------------ function BTThrowingAttack:traverse( ... ) - local blackboard, character = ... + local blackboard, character, _, _, projectileManager = ... - local success = character:enqueueAction( ThrowingAttack( character, blackboard.target )) + local success = character:enqueueAction( ThrowingAttack( character, blackboard.target, projectileManager )) if success then -- Store weapon id for the rearm action. blackboard.weaponID = character:getWeapon():getID() diff --git a/src/items/weapons/ExplosionManager.lua b/src/items/weapons/ExplosionManager.lua index 067b07b8..31ade5eb 100644 --- a/src/items/weapons/ExplosionManager.lua +++ b/src/items/weapons/ExplosionManager.lua @@ -1,29 +1,27 @@ -local Bresenham = require( 'lib.Bresenham' ); -local Messenger = require( 'src.Messenger' ); -local SoundManager = require( 'src.SoundManager' ) -local Util = require( 'src.util.Util' ); +--- +-- @module ExplosionManager +-- -- ------------------------------------------------ --- Module +-- Required Modules -- ------------------------------------------------ -local ExplosionManager = {}; +local Bresenham = require( 'lib.Bresenham' ) +local SoundManager = require( 'src.SoundManager' ) +local Util = require( 'src.util.Util' ) +local Observable = require( 'src.util.Observable' ) -- ------------------------------------------------ --- Constants +-- Module -- ------------------------------------------------ -local DAMAGE_TYPES = require( 'src.constants.DAMAGE_TYPES' ); +local ExplosionManager = Observable:subclass( 'ExplosionManager' ) -- ------------------------------------------------ --- Private Variables +-- Constants -- ------------------------------------------------ -local map; -local explosionLayout; -local explosionIndex = 1; -local timer = 0; -local delay = 0.02; +local DAMAGE_TYPES = require( 'src.constants.DAMAGE_TYPES' ) -- ------------------------------------------------ -- Private Functions @@ -31,54 +29,54 @@ local delay = 0.02; --- -- Determines all tiles which are hit by the explosion. --- @param source (Tile) The origin of the explosion. --- @param list (table) A sequence containing all tiles in the explosion's radius. --- @param radius (radius) The maximum radius of the explosion. --- @return (table) A table containing sub-tables for each line. +-- @tparam Tile source The origin of the explosion. +-- @tparam table list A sequence containing all tiles in the explosion's radius. +-- @tparam number radius The maximum radius of the explosion. +-- @treturn table A table containing sub-tables for each line. -- -local function generateExplosionMap( source, list, radius ) - local sx, sy = source:getPosition(); - local queues = {}; +local function generateExplosionMap( map, source, list, radius ) + local sx, sy = source:getPosition() + local queues = {} -- Iterate over all tiles in the list. Get all tiles between the source of -- the explosion and each tile in the list and store them in a table. for _, tile in ipairs( list ) do - local queue = {}; + local queue = {} -- Randomize the radius to prevent all target tiles to be hit covered. This -- is done to prevent a perfectly circular explosion. - local length = love.math.random( radius ); + local length = love.math.random( radius ) if tile ~= source then - local tx, ty = tile:getPosition(); + local tx, ty = tile:getPosition() -- Cast a ray from the source of the explosion to the target tile. Bresenham.line( sx, sy, tx, ty, function( cx, cy, count ) - local target = map:getTileAt( cx, cy ); + local target = map:getTileAt( cx, cy ) -- Stop at impassable world objects that cover the whole tile. if target:hasWorldObject() and not target:getWorldObject():isDestructible() and target:getWorldObject():getHeight() == 100 then - return false; + return false end -- Stop at the maximum range of the explosion. if length == count then - return false; + return false end -- Store the target tile. - queue[#queue + 1] = target; - return true; + queue[#queue + 1] = target + return true end) end -- Store the table for this specific line. - queues[#queues + 1] = queue; + queues[#queues + 1] = queue end - return queues; + return queues end --- @@ -91,97 +89,93 @@ end -- @return (table) A table containing subtables for each step of the explosion. -- local function generateExplosionSteps( queues, damage, radius ) - local layout = {}; + local layout = {} for i = 1, radius do local steps = {} for _, queue in ipairs( queues ) do - local tile = queue[i]; + local tile = queue[i] if tile then - steps[tile] = damage; + steps[tile] = damage end end - layout[i] = steps; + layout[i] = steps end - return layout; + return layout end -- ------------------------------------------------ --- Public Functions +-- Public Methods -- ------------------------------------------------ +--- +-- Initialises the ExplosionManager. +-- @tparam Map map The map object. +-- +function ExplosionManager:initialize( map ) + Observable.initialize( self ) + + self.map = map + self.timer = 0 + self.delay = 0.02 +end + --- -- Updates the current explosion. -- -function ExplosionManager.update( dt ) - if not explosionLayout then - return; +function ExplosionManager:update( dt ) + if not self.explosionLayout then + return end - timer = timer - dt; + self.timer = self.timer - dt -- If the last step of the explosion has been reached delete the current -- explosion layout. - if not explosionLayout[explosionIndex] then - explosionLayout = nil; - return; + if not self.explosionLayout[self.explosionIndex] then + self.explosionLayout = nil + return end -- Advances the explosion and publishes a message about it. - if explosionLayout and timer <= 0 then + if self.explosionLayout and self.timer <= 0 then -- Notify anyone who cares. - Messenger.publish( 'EXPLOSION', explosionLayout[explosionIndex] ); + self:publish( 'EXPLOSION', self.explosionLayout[self.explosionIndex] ) SoundManager.play( 'sound_explode') -- Damage the hit tiles. - for tile, damage in pairs( explosionLayout[explosionIndex] ) do + for tile, damage in pairs( self.explosionLayout[self.explosionIndex] ) do tile:hit( damage, DAMAGE_TYPES.EXPLOSIVE ) end -- Advance the step index. - explosionIndex = explosionIndex + 1; - timer = delay; + self.explosionIndex = self.explosionIndex + 1 + self.timer = self.delay end end --- --- Initialises the ExplosionManager. --- @param nmap (Map) The map object. --- -function ExplosionManager.init( nmap ) - map = nmap; -end - ---- --- Registers and creates a new explosion. --- @param source (Tile) The explosion's source. --- @param damage (number) The explosion's base damage. --- @param radius (number) The explosion's radius. --- -function ExplosionManager.register( source, damage, radius ) - local list = Util.getTilesInCircle( map, source, radius ); - local queues = generateExplosionMap( source, list, radius ); - explosionLayout = generateExplosionSteps( queues, damage, radius ); - explosionIndex = 1; -end - ---- --- Remove any saved state. +-- Receives events. +-- @tparam string event The received event. +-- @tparam varargs ... Variable arguments. -- -function ExplosionManager.clear() - map = nil; - explosionLayout = nil; - explosionIndex = 1; - timer = 0; - delay = 0.02; +function ExplosionManager:receive( event, ... ) + if event == 'CREATE_EXPLOSION' then + local source, damage, radius = ... + + local list = Util.getTilesInCircle( self.map, source, radius ) + local queues = generateExplosionMap( self.map, source, list, radius ) + self.explosionLayout = generateExplosionSteps( queues, damage, radius ) + self.explosionIndex = 1 + end end --- -- Returns true if all explosions have been handled. --- @return (boolean) False if an explosion is active. +-- @treturn boolean False if an explosion is active. -- -function ExplosionManager.isDone() - if explosionLayout then - return false; +function ExplosionManager:isDone() + if self.explosionLayout then + return false end - return true; + return true end -return ExplosionManager; +return ExplosionManager diff --git a/src/items/weapons/ProjectileManager.lua b/src/items/weapons/ProjectileManager.lua index 6bf671b5..a7c06a01 100644 --- a/src/items/weapons/ProjectileManager.lua +++ b/src/items/weapons/ProjectileManager.lua @@ -1,41 +1,40 @@ -local Log = require( 'src.util.Log' ); -local Messenger = require( 'src.Messenger' ); -local ExplosionManager = require( 'src.items.weapons.ExplosionManager' ); -local Util = require( 'src.util.Util' ) -local Translator = require( 'src.util.Translator' ) +--- +-- @module ProjectileManager +-- -- ------------------------------------------------ --- Module +-- Required Modules -- ------------------------------------------------ -local ProjectileManager = {}; +local Observable = require( 'src.util.Observable' ) +local Log = require( 'src.util.Log' ) +local Util = require( 'src.util.Util' ) +local Translator = require( 'src.util.Translator' ) -- ------------------------------------------------ --- Constants +-- Module -- ------------------------------------------------ -local DAMAGE_TYPES = require( 'src.constants.DAMAGE_TYPES' ); +local ProjectileManager = Observable:subclass( 'ProjectileManager' ) -- ------------------------------------------------ --- Private Variables +-- Constants -- ------------------------------------------------ -local queue; -local map; +local DAMAGE_TYPES = require( 'src.constants.DAMAGE_TYPES' ) -- ------------------------------------------------ -- Private Functions -- ------------------------------------------------ --- --- Removes a projectile from the world and hits a tile with the projectile --- damage. --- @param tile (Tile) The tile to hit. --- @param projectile (Projectile) The projectile to remove. +-- Removes a projectile from the world and hits a tile with the projectile damage. +-- @tparam Tile tile The tile to hit. +-- @tparam Projectile projectile The projectile to remove. -- -local function hitTile( tile, projectile ) +local function hitTile( self, tile, projectile ) if projectile:getDamageType() == DAMAGE_TYPES.EXPLOSIVE then - ExplosionManager.register( tile, projectile:getDamage(), projectile:getEffects():getBlastRadius() ) + self:publish( 'CREATE_EXPLOSION', tile, projectile:getDamage(), projectile:getEffects():getBlastRadius() ) return end @@ -44,32 +43,35 @@ end --- -- Reduces the energy of the projectile with slightly randomized values. --- @params energy (number) The current energy. --- @params reduction (number) The reduction that should be applied. --- @return (number) The modified energy value. +-- @tparam number energy The current energy. +-- @tparam number reduction The reduction that should be applied. +-- @treturn number The modified energy value. -- local function reduceProjectileEnergy( energy, reduction ) - reduction = love.math.random( reduction - 10, reduction + 10 ); + reduction = love.math.random( reduction - 10, reduction + 10 ) reduction = Util.clamp( 1, reduction, 100 ) - return energy - reduction; + return energy - reduction end --- +-- Hits a character. +-- @tparam Tile tile The tile to hit. +-- @tparam Projectile projectile The projectile to remove. -- -local function hitCharacter( tile, projectile ) +local function hitCharacter( self, tile, projectile ) Log.debug( 'Projectile hit character', 'ProjectileManager' ) - hitTile( tile, projectile ) + hitTile( self, tile, projectile ) return true end --- -- Handles how to proceed if the projectile hits a world object. --- @param projectile (Projectile) The projectile to handle. --- @param tile (Tile) The tile to check. --- @param worldObject (WorldObject) The world object to check. --- @treturn boolean Wether to remove the projectile or not. +-- @tparam Projectile projectile The projectile to handle. +-- @tparam Tile tile The tile to check. +-- @tparam WorldObject worldObject The world object to check. +-- @treturn boolean Wether to remove the projectile or not. -- -local function hitWorldObject( projectile, tile, worldObject ) +local function hitWorldObject( self, projectile, tile, worldObject ) if projectile:getHeight() > worldObject:getHeight() then Log.debug( 'Projectile flies over world object', 'ProjectileManager' ) return false @@ -83,35 +85,36 @@ local function hitWorldObject( projectile, tile, worldObject ) -- indestructible world object. This needs to be done to make sure explosions -- occur on the right side of the world object. if projectile:getDamageType() == DAMAGE_TYPES.EXPLOSIVE then - tile = projectile:getPreviousTile(); + tile = projectile:getPreviousTile() end tile:publish( 'MESSAGE_LOG_EVENT', tile, string.format( Translator.getText( 'msg_hit_indestructible_worldobject' ), Translator.getText( worldObject:getID() )), 'INFO' ) - hitTile( tile, projectile ) + hitTile( self, tile, projectile ) return true end Log.debug( 'Projectile hit destructible world object', 'ProjectileManager' ) -- Projectiles passing through world objects lose some of their energy. - local energy = reduceProjectileEnergy( projectile:getEnergy(), worldObject:getEnergyReduction() ); - projectile:setEnergy( energy ); + local energy = reduceProjectileEnergy( projectile:getEnergy(), worldObject:getEnergyReduction() ) + projectile:setEnergy( energy ) tile:publish( 'MESSAGE_LOG_EVENT', tile, string.format( Translator.getText( 'msg_hit_worldobject' ), Translator.getText( worldObject:getID() ), projectile:getDamage() ), 'INFO' ) -- Apply the damage to the tile and only remove it if the energy is 0. - hitTile( tile, projectile ) + hitTile( self, tile, projectile ) return energy <= 0 end --- -- Checks if the projectile has hit any characters or worldobjects. --- @param index (number) The id of the projectile which will be used for removing it. --- @param projectile (Projectile) The projectile to handle. --- @param tile (Tile) The tile to check. --- @param character (Character) The character who fired this projectile. +-- @tparam ProjectileQueue queue The ProjectileQueue to process. +-- @tparam number index The id of the projectile which will be used for removing it. +-- @tparam Projectile projectile The projectile to handle. +-- @tparam Tile tile The tile to check. +-- @tparam Character character The character who fired this projectile. -- -local function checkForHits( index, projectile, tile ) +local function checkForHits( self, queue, index, projectile, tile ) -- Stop movement and remove the projectile if it has reached the map border. if not tile then queue:removeProjectile( index ) @@ -121,14 +124,14 @@ local function checkForHits( index, projectile, tile ) local remove = false if tile:hasCharacter() then - remove = hitCharacter( tile, projectile ) + remove = hitCharacter( self, tile, projectile ) elseif tile:hasWorldObject() then - remove = hitWorldObject( projectile, tile, tile:getWorldObject() ) + remove = hitWorldObject( self, projectile, tile, tile:getWorldObject() ) end if not remove and projectile:hasReachedTarget() then Log.debug( 'Projectile reached target tile', 'ProjectileManager' ) - hitTile( tile, projectile ) + hitTile( self, tile, projectile ) remove = true end @@ -143,66 +146,60 @@ end --- -- Initialise the ProjectileManager. --- @param nmap (Map) The game's map. +-- @tparam Map map The game's map. -- -function ProjectileManager.init( nmap ) - map = nmap; +function ProjectileManager:initialize( map ) + Observable.initialize( self ) + + self.map = map end --- -- Updates the ProjectileManager which handles a ProjectileQueue for spawning -- projectiles and the interaction of each projectile with the game world. --- @param dt (number) The time since the last frame. +-- @tparam number dt The time since the last frame. -- -function ProjectileManager.update( dt ) - if not queue then - return; +function ProjectileManager:update( dt ) + if not self.queue then + return end -- Updates the time of the projectile queue which handles spawning of -- new projectiles. - queue:update( dt ); + self.queue:update( dt ) -- Update the projectiles currently in the game world. - for i, projectile in pairs( queue:getProjectiles() ) do - projectile:update( dt ); + for i, projectile in pairs( self.queue:getProjectiles() ) do + projectile:update( dt ) -- If the projectile has moved notify the world and check if it has -- hit anything. - if projectile:hasMoved( map ) then - projectile:updateTile( map ); - Messenger.publish( 'PROJECTILE_MOVED', projectile ); + if projectile:hasMoved( self.map ) then + projectile:updateTile( self.map ) + self:publish( 'PROJECTILE_MOVED', projectile ) - checkForHits( i, projectile, projectile:getTile() ) + checkForHits( self, self.queue, i, projectile, projectile:getTile() ) end end end --- -- Registers a new projectile queue. --- @param nqueue (ProjectileQueue) The ProjectileQueue to process. --- -function ProjectileManager.register( nqueue ) - queue = nqueue -end - ---- --- Remove any saved state. +-- @tparam ProjectileQueue queue The ProjectileQueue to process. -- -function ProjectileManager.clear() - queue = nil; - map = nil; +function ProjectileManager:register( queue ) + self.queue = queue end --- -- Returns true if there isn't a queue or if the current queue has been processed. --- @return (boolean) True if the projectile manager has processed the queue. +-- @treturn boolean True if the projectile manager has processed the queue. -- -function ProjectileManager.isDone() - if not queue then - return true; +function ProjectileManager:isDone() + if not self.queue then + return true end - return queue:isDone(); + return self.queue:isDone() end -return ProjectileManager; +return ProjectileManager diff --git a/src/map/worldobjects/WorldObject.lua b/src/map/worldobjects/WorldObject.lua index 8e862909..35915b75 100644 --- a/src/map/worldobjects/WorldObject.lua +++ b/src/map/worldobjects/WorldObject.lua @@ -246,7 +246,10 @@ end function WorldObject:setBlocksVision( blocksVision ) self.blocksVision = blocksVision - self:publish( 'TILE_UPDATED', self:getTile() ) + -- Send no update if the map isn't assembled already during map loading. + if self:getMap() then + self:publish( 'TILE_UPDATED', self:getTile() ) + end end --- diff --git a/src/turnbased/helpers/AttackInput.lua b/src/turnbased/helpers/AttackInput.lua index 36489607..0ee9e22a 100644 --- a/src/turnbased/helpers/AttackInput.lua +++ b/src/turnbased/helpers/AttackInput.lua @@ -34,7 +34,7 @@ local WEAPON_TYPES = require( 'src.constants.WEAPON_TYPES' ) -- @tparam Character character The character to create the action for. -- @treturn boolean True if an action was created, false otherwise. -- -function AttackInput:request( target, character ) +function AttackInput:request( target, character, projectileManager ) -- Prevent characters from attacking themselves. if target == character:getTile() then return false @@ -54,13 +54,13 @@ function AttackInput:request( target, character ) -- Handle Thrown weapons. if weapon:getSubType() == WEAPON_TYPES.THROWN then - character:enqueueAction( ThrowingAttack( character, target )) + character:enqueueAction( ThrowingAttack( character, target, projectileManager )) character:enqueueAction( Rearm( character, weapon:getID() )) end -- Handle Ranged weapons. if weapon:getSubType() == WEAPON_TYPES.RANGED then - character:enqueueAction( RangedAttack( character, target )) + character:enqueueAction( RangedAttack( character, target, projectileManager )) end return true diff --git a/src/turnbased/states/ExecutionState.lua b/src/turnbased/states/ExecutionState.lua index 8a97879e..e357fbcb 100644 --- a/src/turnbased/states/ExecutionState.lua +++ b/src/turnbased/states/ExecutionState.lua @@ -7,8 +7,6 @@ -- ------------------------------------------------ local Class = require( 'lib.Middleclass' ) -local ProjectileManager = require( 'src.items.weapons.ProjectileManager' ) -local ExplosionManager = require( 'src.items.weapons.ExplosionManager' ) local Log = require( 'src.util.Log' ) -- ------------------------------------------------ @@ -34,20 +32,23 @@ function ExecutionState:initialize( stateManager ) self.actionTimer = 0 end -function ExecutionState:enter( factions, character ) - self.factions = factions +function ExecutionState:enter( factions, character, explosionManager, projectileManager ) + self.factions = factions self.character = character - self.delay = character:getFaction():isAIControlled() and AI_DELAY or PLAYER_DELAY + self.explosionManager = explosionManager + self.projectileManager = projectileManager + + self.delay = character:getFaction():isAIControlled() and AI_DELAY or PLAYER_DELAY end function ExecutionState:update( dt ) - if not ProjectileManager.isDone() then - ProjectileManager.update( dt ) + if not self.projectileManager:isDone() then + self.projectileManager:update( dt ) return end - if not ExplosionManager.isDone() then - ExplosionManager.update( dt ) + if not self.explosionManager:isDone() then + self.explosionManager:update( dt ) return end diff --git a/src/turnbased/states/PlanningState.lua b/src/turnbased/states/PlanningState.lua index f5dc6d5e..e2bf45bd 100644 --- a/src/turnbased/states/PlanningState.lua +++ b/src/turnbased/states/PlanningState.lua @@ -71,7 +71,7 @@ local function handleInputSelectionActions( input, inputStateHandler, character return true end -local function handleOtherActions( input, stateManager, inputStateHandler, factions, character ) +local function handleOtherActions( input, stateManager, inputStateHandler, factions, character, explosionManager, projectileManager ) if input == 'next_character' then inputStateHandler:switch( 'movement' ) factions:getFaction():nextCharacter() @@ -90,7 +90,7 @@ local function handleOtherActions( input, stateManager, inputStateHandler, facti if input == 'open_inventory_screen' then character:enqueueAction( OpenInventory( character, character:getTile() )) - stateManager:push( 'execution', factions, character ) + stateManager:push( 'execution', factions, character, explosionManager, projectileManager ) return true elseif input == 'open_health_screen' then ScreenManager.push( 'playerInfo', character ) @@ -127,8 +127,10 @@ function PlanningState:initialize( stateManager ) self.inputStateHandler:switch( 'movement' ) end -function PlanningState:enter( factions ) +function PlanningState:enter( factions, explosionManager, projectileManager ) self.factions = factions + self.explosionManager = explosionManager + self.projectileManager = projectileManager end function PlanningState:update() @@ -148,7 +150,7 @@ function PlanningState:input( input ) end if handleCharacterActions( input, character ) then - self.stateManager:push( 'execution', self.factions, character ) + self.stateManager:push( 'execution', self.factions, character, self.explosionManager, self.projectileManager ) return end @@ -156,7 +158,7 @@ function PlanningState:input( input ) return end - if handleOtherActions( input, self.stateManager, self.inputStateHandler, self.factions, character ) then + if handleOtherActions( input, self.stateManager, self.inputStateHandler, self.factions, character, self.explosionManager, self.projectileManager ) then return end end @@ -174,9 +176,9 @@ function PlanningState:selectTile( tile, button ) end -- Request actions to execute. - local execute = self.inputStateHandler:getState():request( tile, character ) + local execute = self.inputStateHandler:getState():request( tile, character, self.projectileManager ) if execute then - self.stateManager:push( 'execution', self.factions, character ) + self.stateManager:push( 'execution', self.factions, character, self.explosionManager, self.projectileManager ) end end diff --git a/src/ui/overlays/OverlayPainter.lua b/src/ui/overlays/OverlayPainter.lua index 8dacef77..d2d710c0 100644 --- a/src/ui/overlays/OverlayPainter.lua +++ b/src/ui/overlays/OverlayPainter.lua @@ -93,7 +93,7 @@ function OverlayPainter:initialize( game, camera ) self.game = game self.camera = camera - self.particleLayer = ParticleLayer() + self.particleLayer = ParticleLayer( game:getExplosionManager(), game:getProjectileManager() ) self.pulser = Pulser( 2, 0.2, 0.6 ) self.coneOverlay = ConeOverlay( self.game, self.pulser, self.camera ) self.pathOverlay = PathOverlay( self.game, self.pulser ) diff --git a/src/ui/overlays/ParticleLayer.lua b/src/ui/overlays/ParticleLayer.lua index d15118cd..53a6a1a3 100644 --- a/src/ui/overlays/ParticleLayer.lua +++ b/src/ui/overlays/ParticleLayer.lua @@ -7,7 +7,6 @@ -- ------------------------------------------------ local Class = require( 'lib.Middleclass' ) -local Messenger = require( 'src.Messenger' ) local Particle = require( 'src.ui.overlays.Particle' ) local ObjectPool = require( 'src.util.ObjectPool' ) @@ -34,11 +33,31 @@ end -- Public Methods -- ------------------------------------------------ -function ParticleLayer:initialize() +function ParticleLayer:initialize( explosionManager, projectileManager ) + self.explosionManager = explosionManager + self.explosionManager:observe( self ) + + self.projectileManager = projectileManager + self.projectileManager:observe( self ) + self.grid = {} self.particles = ObjectPool( Particle ) +end - Messenger.observe( 'PROJECTILE_MOVED', function( ... ) +function ParticleLayer:receive( event, ... ) + if event == 'EXPLOSION' then + local generation = ... + for tile, _ in pairs( generation ) do + local r = 0.9 + local g = love.math.random( 0.39, 0.58 ) + local b = 0 + local a = love.math.random( 0.78, 0.9 ) + local fade = math.max( 0.95, love.math.random( 2.5 )) + addParticleEffect( self.grid, self.particles, tile:getX(), tile:getY(), r, g, b, a, fade ) + end + return + end + if event == 'PROJECTILE_MOVED' then local projectile = ... local tile = projectile:getTile() if tile then @@ -51,19 +70,7 @@ function ParticleLayer:initialize() addParticleEffect( self.grid, self.particles, tile:getX(), tile:getY(), 0.87450, 0.44313, 0.14901, 0.78, 0.5 ) end end - end) - - Messenger.observe( 'EXPLOSION', function( ... ) - local generation = ... - for tile, _ in pairs( generation ) do - local r = 0.9 - local g = love.math.random( 0.39, 0.58 ) - local b = 0 - local a = love.math.random( 0.78, 0.9 ) - local fade = math.max( 0.95, love.math.random( 2.5 )) - addParticleEffect( self.grid, self.particles, tile:getX(), tile:getY(), r, g, b, a, fade ) - end - end) + end end function ParticleLayer:update( dt ) diff --git a/src/ui/screens/CombatScreen.lua b/src/ui/screens/CombatScreen.lua index 707ae297..c1410a6b 100644 --- a/src/ui/screens/CombatScreen.lua +++ b/src/ui/screens/CombatScreen.lua @@ -122,10 +122,6 @@ function CombatScreen:mousefocus( f ) end end -function CombatScreen:close() - self.combatState:close() -end - function CombatScreen:resize( _, _ ) self.userInterface:resize() end diff --git a/src/ui/texturepacks/Font.lua b/src/ui/texturepacks/Font.lua index 366ddd2a..38708b84 100644 --- a/src/ui/texturepacks/Font.lua +++ b/src/ui/texturepacks/Font.lua @@ -20,18 +20,67 @@ local Font = Class( 'Font' ) -- Public Methods -- ------------------------------------------------ +--- +-- Assembles the string containing all glyphs for the ImageFont. +-- @tparam table charsets A sequence containing the different charset definitions. +-- @treturn string The assembled glyph strings. +-- +local function assembleGlyphs( charsets ) + local glyphs = '' + for _, charset in ipairs( charsets ) do + glyphs = glyphs .. charset.glyphs + end + return glyphs +end + +--- +-- Assembles the ImageData for the ImageFont. +-- @tparam number height The height of the ImageFont. +-- @tparam string path The path of the image files to load. +-- @tparam table charsets A sequence containing the different charset definitions. +-- @treturn ImageData The assembled ImageData. +-- +local function assembleImageData( height, path, charsets ) + local fontWidth = 0 + local imageDatas = {} + + -- Check how many pixels we need for the new ImageData and store the + -- different ImageData objects. + for _, definition in ipairs( charsets ) do + local data = love.image.newImageData( path .. definition.source ) + fontWidth = fontWidth + data:getWidth() + imageDatas[#imageDatas + 1] = data + end + + -- Create the empty ImageData. + local fontImageData = love.image.newImageData( fontWidth, height ) + + -- Paste the different ImageDatas to the font's ImageData. + local offset = 0 + for _, data in ipairs( imageDatas ) do + fontImageData:paste( data, offset, 0, 0, 0, data:getDimensions() ) + offset = offset + data:getWidth() + end + + return fontImageData +end + --- -- Creates a new instance of the Font class. --- @tparam string source The path to load the imagefont file from. --- @tparam string glyphs The glyph definition. --- @tparam number gw The width of one glyph. --- @tparam number gh The height of one glpyh. --- @treturn Font The new Font instance. +-- @tparam string path The path to load the imagefont file from. +-- @tparam table def The font definition. +-- @treturn Font The new Font instance. -- -function Font:initialize( source, glyphs, gw, gh ) - self.font = love.graphics.newImageFont( source, glyphs ) - self.glyphWidth = gw - self.glyphHeight = gh +function Font:initialize( path, def ) + self.glyphWidth = def.width + self.glyphHeight = def.height + + -- Assemble the parts of the ImageFont. + local glyphs = assembleGlyphs( def.charsets ) + local imageData = assembleImageData( def.height, path, def.charsets ) + + -- Create the ImageFont. + self.font = love.graphics.newImageFont( imageData, glyphs ) end --- diff --git a/src/ui/texturepacks/TexturePack.lua b/src/ui/texturepacks/TexturePack.lua index fa5a0116..7f1fd08e 100644 --- a/src/ui/texturepacks/TexturePack.lua +++ b/src/ui/texturepacks/TexturePack.lua @@ -24,9 +24,8 @@ function TexturePack:initialize( path, source, spriteInfos, colorInfos ) self.name = source.name -- Generate font. - local f = source.font - self.font = Font( path .. f.source, f.glyphs.source, f.glyphs.width, f.glyphs.height ) - self.glyphWidth, self.glyphHeight = f.glyphs.width, f.glyphs.height + self.font = Font( path, source.font ) + self.glyphWidth, self.glyphHeight = source.font.width, source.font.height -- Generate tileset. local t = source.tileset diff --git a/src/ui/texturepacks/TexturePacks.lua b/src/ui/texturepacks/TexturePacks.lua index b91e9904..2df0a001 100644 --- a/src/ui/texturepacks/TexturePacks.lua +++ b/src/ui/texturepacks/TexturePacks.lua @@ -32,7 +32,8 @@ local DEFAULT = { COLORS = 'colors.lua', WARNING = 'README.txt', SPRITES = 'sprites.lua', - IMAGEFONT = 'imagefont.png', + IMAGEFONT_LATIN_BASIC = 'imagefont_latin_basic.png', + IMAGEFONT_LATIN_1 = 'imagefont_latin_1.png', SPRITESHEET = 'spritesheet.png', } @@ -47,36 +48,6 @@ local current -- Private Functions -- ------------------------------------------------ ---- --- Checks if the loaded module provides all the necessary fields. --- @tparam table module The loaded module to check. --- @treturn boolean True if the module is valid. --- -local function validate( module ) - if not module.name - or not module.font - or not module.tileset then - return false - end - - if not module.font.source - or not module.font.glyphs - or not module.tileset.source - or not module.tileset.tiles then - return false - end - - if not module.font.glyphs.source - or not module.font.glyphs.width - or not module.font.glyphs.height - or not module.tileset.tiles.width - or not module.tileset.tiles.height then - return false - end - - return true -end - --- -- Loads a texture pack. -- @tparam string src The path to load the templates from. @@ -86,7 +57,7 @@ end local function load( src ) local path = src .. INFO_FILE_NAME local module = require( path ) - if not module or not validate( module ) then + if not module then return false end @@ -163,7 +134,8 @@ local function copyDefaultTexturePack() copyFile( source, target, DEFAULT.COLORS ) copyFile( source, target, DEFAULT.SPRITES ) copyFile( source, target, DEFAULT.WARNING ) - copyFile( source, target, DEFAULT.IMAGEFONT ) + copyFile( source, target, DEFAULT.IMAGEFONT_LATIN_BASIC ) + copyFile( source, target, DEFAULT.IMAGEFONT_LATIN_1 ) copyFile( source, target, DEFAULT.SPRITESHEET ) end diff --git a/version.lua b/version.lua index 19e59ca8..29fcbe9c 100644 --- a/version.lua +++ b/version.lua @@ -1,8 +1,8 @@ local version = { major = 0, minor = 16, - patch = 0, - build = 1615, + patch = 1, + build = 1627, } return string.format( "%d.%d.%d.%d", version.major, version.minor, version.patch, version.build );