Compare commits

..

22 Commits

Author SHA1 Message Date
57d8fa8cc5 Updated gitignore. 2025-10-13 00:40:13 -04:00
777b70d1ab Added dialog windows and control settings.
Lots of comments and changes in UI code in general.
2025-10-13 00:28:12 -04:00
4ecaaf1b50 Added comments to the horizontal bar. 2025-10-12 12:11:02 -04:00
4ad280a820 Added sound manager. 2025-10-12 01:25:04 -04:00
d3d1fa1430 Added horizontal bars to UI components. 2025-10-12 00:38:29 -04:00
7cfffde56d Added generic layout class and HBox. 2025-10-11 22:54:09 -04:00
3f821d4258 Fixed aspect ratio in splash images. 2025-10-11 22:53:46 -04:00
9561db2847 Added options menu and menu transitions. 2025-10-06 01:07:33 -04:00
719a9cd0f6 Removed hardcoded screen width in VBox class. 2025-10-05 23:34:52 -04:00
2dea06f8c7 Added labels and flushed-right UI elements. 2025-10-05 22:45:55 -04:00
479ff952cf Renamed ButtonGroup to VBox. 2025-10-05 22:15:11 -04:00
bed93d1de4 Added text buttons and button groups. 2025-10-05 22:11:47 -04:00
82d2a89a0a Added assets manager singleton. 2025-10-05 17:52:28 -04:00
03b5d1dafb Reorganized code and assets. 2025-10-05 15:24:48 -04:00
7f8d79e00f Many changes all around. Started main menu.
Added Drawable, Sprite and SoundEffect classes.
Added comments to Fader class.
2025-10-05 02:26:39 -04:00
fe8944b526 Fixed wrong call to load_settings instead of save. 2025-10-04 05:03:51 -04:00
c0d25f1d47 Code formatting and comments across the board. 2025-10-04 05:02:39 -04:00
845c696899 Added FPS counter to debug info. 2025-10-04 04:48:33 -04:00
f71466d7fa Adjusted intro times. 2025-10-04 04:48:20 -04:00
cfb04372de Added black backdrop to debug info. 2025-10-04 04:38:41 -04:00
8662424c80 Fixed invalid settings key error message. 2025-09-28 19:36:36 -04:00
5aa71ac03f Added sample DOSBox config file. 2025-09-28 19:32:18 -04:00
58 changed files with 2702 additions and 117 deletions

2
.gitignore vendored
View File

@@ -111,4 +111,4 @@ flycheck_*.el
# Swap Files #
.*.kate-swp
.swp.*
.kateproject*

View File

@@ -6,16 +6,19 @@ A dungeon crawler game made with LoveDOS for the DOSember Game Jam https://itch.
### Sprites
- [B&W Ornamental Cursor by qubodup](https://opengameart.org/content/bw-ornamental-cursor-19x19)
- [Microchip texture by ZeptoBARS](https://opengameart.org/content/microchip-texture)
- [Eris in colour by Stephen](https://openclipart.org/detail/329762/eris-in-colour)
### Textures
- [Microchip texture by ZeptoBARS](https://opengameart.org/content/microchip-texture)
- [Assorted textures by Makkon](https://www.slipseer.com/index.php?resources/makkon-textures.28/)
### Sound effects
- [Click.wav by frosty ham](https://opengameart.org/content/click-0)
- [Sci-Fi Sound Effects Library by Little Robot Sound Factory](https://opengameart.org/content/sci-fi-sound-effects-library)
### Music
- [Dogs of Cyberspace by congusbongus](https://modarchive.org/index.php?request=view_by_moduleid&query=207032)
- [Eskisky Zhi by Immorpher](https://immorpher.bandcamp.com/track/eskisky-zhi)
### Fonts

BIN
assets/bgm/eskisky.wav Normal file

Binary file not shown.

BIN
assets/imgs/concb03.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

BIN
assets/imgs/floppy.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

Before

Width:  |  Height:  |  Size: 347 B

After

Width:  |  Height:  |  Size: 347 B

BIN
assets/imgs/splash.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

BIN
assets/imgs/tchcmp1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 33 KiB

BIN
assets/imgs/wally.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

Binary file not shown.

242
dosbox.conf Normal file
View File

@@ -0,0 +1,242 @@
# This is the configurationfile for DOSBox 0.74. (Please use the latest version of DOSBox)
# Lines starting with a # are commentlines and are ignored by DOSBox.
# They are used to (briefly) document the effect of each option.
[sdl]
# fullscreen: Start dosbox directly in fullscreen. (Press ALT-Enter to go back)
# fulldouble: Use double buffering in fullscreen. It can reduce screen flickering, but it can also result in a slow DOSBox.
# fullresolution: What resolution to use for fullscreen: original or fixed size (e.g. 1024x768).
# Using your monitor's native resolution with aspect=true might give the best results.
# If you end up with small window on a large screen, try an output different from surface.
# windowresolution: Scale the window to this size IF the output device supports hardware scaling.
# (output=surface does not!)
# output: What video system to use for output.
# Possible values: surface, overlay, opengl, openglnb, ddraw.
# autolock: Mouse will automatically lock, if you click on the screen. (Press CTRL-F10 to unlock)
# sensitivity: Mouse sensitivity.
# waitonerror: Wait before closing the console if dosbox has an error.
# priority: Priority levels for dosbox. Second entry behind the comma is for when dosbox is not focused/minimized.
# pause is only valid for the second entry.
# Possible values: lowest, lower, normal, higher, highest, pause.
# mapperfile: File used to load/save the key/event mappings from. Resetmapper only works with the defaul value.
# usescancodes: Avoid usage of symkeys, might not work on all operating systems.
fullscreen=false
fulldouble=true
fullresolution=original
windowresolution=original
output=opengl
autolock=true
sensitivity=100
waitonerror=true
priority=higher,normal
mapperfile=mapper.map
usescancodes=true
[dosbox]
# language: Select another language file.
# machine: The type of machine tries to emulate.
# Possible values: hercules, cga, tandy, pcjr, ega, vgaonly, svga_s3, svga_et3000, svga_et4000, svga_paradise, vesa_nolfb, vesa_oldvbe.
# captures: Directory where things like wave, midi, screenshot get captured.
# memsize: Amount of memory DOSBox has in megabytes.
# This value is best left at its default to avoid problems with some games,
# though few games might require a higher value.
# There is generally no speed advantage when raising this value.
language=
machine=svga_s3
captures=capture
memsize=128
[render]
# frameskip: How many frames DOSBox skips before drawing one.
# aspect: Do aspect correction, if your output method doesn't support scaling this can slow things down!.
# scaler: Scaler used to enlarge/enhance low resolution modes.
# If 'forced' is appended, then the scaler will be used even if the result might not be desired.
# Possible values: none, normal2x, normal3x, advmame2x, advmame3x, advinterp2x, advinterp3x, hq2x, hq3x, 2xsai, super2xsai, supereagle, tv2x, tv3x, rgb2x, rgb3x, scan2x, scan3x.
frameskip=0
aspect=true
scaler=normal2x
[cpu]
# core: CPU Core used in emulation. auto will switch to dynamic if available and appropriate.
# Possible values: auto, dynamic, normal, simple.
# cputype: CPU Type used in emulation. auto is the fastest choice.
# Possible values: auto, 386, 386_slow, 486_slow, pentium_slow, 386_prefetch.
# cycles: Amount of instructions DOSBox tries to emulate each millisecond.
# Setting this value too high results in sound dropouts and lags.
# Cycles can be set in 3 ways:
# 'auto' tries to guess what a game needs.
# It usually works, but can fail for certain games.
# 'fixed #number' will set a fixed amount of cycles. This is what you usually need if 'auto' fails.
# (Example: fixed 4000).
# 'max' will allocate as much cycles as your computer is able to handle.
#
# Possible values: auto, fixed, max.
# cycleup: Amount of cycles to decrease/increase with keycombo.(CTRL-F11/CTRL-F12)
# cycledown: Setting it lower than 100 will be a percentage.
core=auto
cputype=auto
cycles=max
cycleup=1000
cycledown=1000
[mixer]
# nosound: Enable silent mode, sound is still emulated though.
# rate: Mixer sample rate, setting any device's rate higher than this will probably lower their sound quality.
# Possible values: 44100, 48000, 32000, 22050, 16000, 11025, 8000, 49716.
# blocksize: Mixer block size, larger blocks might help sound stuttering but sound will also be more lagged.
# Possible values: 1024, 2048, 4096, 8192, 512, 256.
# prebuffer: How many milliseconds of data to keep on top of the blocksize.
nosound=false
rate=44100
blocksize=1024
prebuffer=240
[midi]
# mpu401: Type of MPU-401 to emulate.
# Possible values: intelligent, uart, none.
# mididevice: Device that will receive the MIDI data from MPU-401.
# Possible values: default, win32, alsa, oss, coreaudio, coremidi, none.
# midiconfig: Special configuration options for the device driver. This is usually the id of the device you want to use.
# See the README/Manual for more details.
mpu401=intelligent
mididevice=default
midiconfig=
[sblaster]
# sbtype: Type of Soundblaster to emulate. gb is Gameblaster.
# Possible values: sb1, sb2, sbpro1, sbpro2, sb16, gb, none.
# sbbase: The IO address of the soundblaster.
# Possible values: 220, 240, 260, 280, 2a0, 2c0, 2e0, 300.
# irq: The IRQ number of the soundblaster.
# Possible values: 7, 5, 3, 9, 10, 11, 12.
# dma: The DMA number of the soundblaster.
# Possible values: 1, 5, 0, 3, 6, 7.
# hdma: The High DMA number of the soundblaster.
# Possible values: 1, 5, 0, 3, 6, 7.
# sbmixer: Allow the soundblaster mixer to modify the DOSBox mixer.
# oplmode: Type of OPL emulation. On 'auto' the mode is determined by sblaster type. All OPL modes are Adlib-compatible, except for 'cms'.
# Possible values: auto, cms, opl2, dualopl2, opl3, none.
# oplemu: Provider for the OPL emulation. compat might provide better quality (see oplrate as well).
# Possible values: default, compat, fast.
# oplrate: Sample rate of OPL music emulation. Use 49716 for highest quality (set the mixer rate accordingly).
# Possible values: 44100, 49716, 48000, 32000, 22050, 16000, 11025, 8000.
sbtype=sb16
sbbase=220
irq=7
dma=1
hdma=5
sbmixer=true
oplmode=opl2
oplemu=compat
oplrate=44100
[gus]
# gus: Enable the Gravis Ultrasound emulation.
# gusrate: Sample rate of Ultrasound emulation.
# Possible values: 44100, 48000, 32000, 22050, 16000, 11025, 8000, 49716.
# gusbase: The IO base address of the Gravis Ultrasound.
# Possible values: 240, 220, 260, 280, 2a0, 2c0, 2e0, 300.
# gusirq: The IRQ number of the Gravis Ultrasound.
# Possible values: 5, 3, 7, 9, 10, 11, 12.
# gusdma: The DMA channel of the Gravis Ultrasound.
# Possible values: 3, 0, 1, 5, 6, 7.
# ultradir: Path to Ultrasound directory. In this directory
# there should be a MIDI directory that contains
# the patch files for GUS playback. Patch sets used
# with Timidity should work fine.
gus=false
gusrate=44100
gusbase=240
gusirq=5
gusdma=3
ultradir=C:\ULTRASND
[speaker]
# pcspeaker: Enable PC-Speaker emulation.
# pcrate: Sample rate of the PC-Speaker sound generation.
# Possible values: 44100, 48000, 32000, 22050, 16000, 11025, 8000, 49716.
# tandy: Enable Tandy Sound System emulation. For 'auto', emulation is present only if machine is set to 'tandy'.
# Possible values: auto, on, off.
# tandyrate: Sample rate of the Tandy 3-Voice generation.
# Possible values: 44100, 48000, 32000, 22050, 16000, 11025, 8000, 49716.
# disney: Enable Disney Sound Source emulation. (Covox Voice Master and Speech Thing compatible).
pcspeaker=true
pcrate=44100
tandy=auto
tandyrate=44100
disney=true
[joystick]
# joysticktype: Type of joystick to emulate: auto (default), none,
# 2axis (supports two joysticks),
# 4axis (supports one joystick, first joystick used),
# 4axis_2 (supports one joystick, second joystick used),
# fcs (Thrustmaster), ch (CH Flightstick).
# none disables joystick emulation.
# auto chooses emulation depending on real joystick(s).
# (Remember to reset dosbox's mapperfile if you saved it earlier)
# Possible values: auto, 2axis, 4axis, 4axis_2, fcs, ch, none.
# timed: enable timed intervals for axis. Experiment with this option, if your joystick drifts (away).
# autofire: continuously fires as long as you keep the button pressed.
# swap34: swap the 3rd and the 4th axis. can be useful for certain joysticks.
# buttonwrap: enable button wrapping at the number of emulated buttons.
joysticktype=auto
timed=true
autofire=false
swap34=false
buttonwrap=false
[serial]
# serial1: set type of device connected to com port.
# Can be disabled, dummy, modem, nullmodem, directserial.
# Additional parameters must be in the same line in the form of
# parameter:value. Parameter for all types is irq (optional).
# for directserial: realport (required), rxdelay (optional).
# (realport:COM1 realport:ttyS0).
# for modem: listenport (optional).
# for nullmodem: server, rxdelay, txdelay, telnet, usedtr,
# transparent, port, inhsocket (all optional).
# Example: serial1=modem listenport:5000
# Possible values: dummy, disabled, modem, nullmodem, directserial.
# serial2: see serial1
# Possible values: dummy, disabled, modem, nullmodem, directserial.
# serial3: see serial1
# Possible values: dummy, disabled, modem, nullmodem, directserial.
# serial4: see serial1
# Possible values: dummy, disabled, modem, nullmodem, directserial.
serial1=dummy
serial2=dummy
serial3=disabled
serial4=disabled
[dos]
# xms: Enable XMS support.
# ems: Enable EMS support.
# umb: Enable UMB support.
# keyboardlayout: Language code of the keyboard layout (or none).
xms=true
ems=true
umb=true
keyboardlayout=auto
[ipx]
# ipx: Enable ipx over UDP/IP emulation.
Enable=0
Connection=0
ipx=false

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

97
licenses/OFL.TXT Normal file
View File

@@ -0,0 +1,97 @@
Copyright (c) <dates>, <Copyright Holder> (<URL|email>),
with Reserved Font Name <Reserved Font Name>.
Copyright (c) <dates>, <additional Copyright Holder> (<URL|email>),
with Reserved Font Name <additional Reserved Font Name>.
Copyright (c) <dates>, <additional Copyright Holder> (<URL|email>).
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
https://openfontlicense.org
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

View File

@@ -2,10 +2,10 @@
-- Imports
------------------------------------------------------------------------------
local love = require 'love'
local game_states = require 'src.states'
local settings = require 'src.utils.settings'
local Fader = require 'src.utils.fader'
local love = require 'love'
local game_states = require 'src.states'
local settings = require 'src.utils.settings'
local Fader = require 'src.graphics.fader'
------------------------------------------------------------------------------
@@ -13,9 +13,9 @@ local Fader = require 'src.utils.fader'
------------------------------------------------------------------------------
Current_state = 1
Fade = Fader()
Change_state = false
Debug = false
Fade = Fader()
Change_state = false
Debug = false
------------------------------------------------------------------------------
@@ -24,8 +24,8 @@ Debug = false
local function parse_args(args)
for _, v in pairs(args) do
-- Enable debug mode.
if v == '-debug' then Debug = true end
if v == '-debug' then Debug = true end
if v == '-skip-intro' then Current_state = 2 end
end
end
@@ -37,7 +37,7 @@ end
function love.load(args)
parse_args(args)
-- Obtain the settigns for the game.
-- Obtain the settings for the game.
settings:load_settings()
-- Load the assets of the intro game state.
@@ -67,14 +67,13 @@ function love.update(dt)
love.event.quit()
-- Save the settings before quitting just in case.
settings:load_settings()
settings:save_settings()
end
else
-- If the new state exists then unload it's data and set the new state.
if Change_state and Fade.done then
game_states[Current_state]:unload(dt)
Current_state = new_state
game_states[Current_state]:load()
Change_state = false
Fade:fade_in()
end
@@ -88,9 +87,14 @@ function love.draw()
Fade:draw()
if Debug then
-- love.graphics.setColor(0, 0, 0)
-- love.graphics.rectangle('fill', 0, 0, 140, 50)
love.graphics.setColor(255, 250, 0)
love.graphics.print(string.format('OS: %s', love.system.getOS()), 5, 5)
love.graphics.print(string.format('Love version: %s', love.getVersion()), 5, 15)
love.graphics.print(string.format('Memory usage: %s KiB', love.system.getMemUsage()), 5, 25)
love.graphics.print(string.format('FPS: %.2f', 1.0 / love.timer.getAverageDelta()), 5, 35)
love.graphics.setColor()
end
end
@@ -130,7 +134,7 @@ end
function love.mousepressed(x, y, btn)
-- Send events to the active game state if there is no fade active.
if Fade.done then
game_states[Current_state]:mousemoved(x, y, btn)
game_states[Current_state]:mousepressed(x, y, btn)
end
end

58
src/graphics/drawable.lua Normal file
View File

@@ -0,0 +1,58 @@
------------------------------------------------------------------------------
-- Imports
------------------------------------------------------------------------------
local make_class = require 'src.utils.classes'
------------------------------------------------------------------------------
-- Class definitions
------------------------------------------------------------------------------
---@class Drawable
local Drawable = make_class()
------------------------------------------------------------------------------
-- Class methods
------------------------------------------------------------------------------
function Drawable:_init()
end
function Drawable:update(_)
end
function Drawable:draw()
end
function Drawable:keypressed(_, _ , _)
end
function Drawable:textinput(_)
end
function Drawable:keyreleased(_, _)
end
function Drawable:mousemoved(_, _, _, _)
end
function Drawable:mousepressed(_, _, _)
end
function Drawable:mousereleased(_, _, _)
end
------------------------------------------------------------------------------
-- Module return
------------------------------------------------------------------------------
return Drawable

View File

@@ -2,7 +2,7 @@
-- Imports
------------------------------------------------------------------------------
local love = require 'love'
local love = require 'love'
local make_class = require 'src.utils.classes'
@@ -18,16 +18,16 @@ local Fader = make_class()
------------------------------------------------------------------------------
function Fader:_init()
self.t = 0.0
self.step = 0
self.done = true
self.reverse = false
self.t = 0.0
self.step = 0
self.done = true
self.reverse = false
self.callback = nil
end
function Fader:_reset()
self.t = 0
self.t = 0
self.step = 0
self.done = false
end
@@ -35,29 +35,35 @@ end
function Fader:fade_in(callback)
self:_reset()
self.reverse = true
self.reverse = true
self.callback = callback
end
function Fader:fade_out(callback)
self:_reset()
self.reverse = false
self.reverse = false
self.callback = callback
end
function Fader:update(dt)
-- While the fader is active.
if not self.done then
-- Update the internal timer.
self.t = self.t + dt
-- Advance the fade if enough time has passed.
if self.t >= 0.009 then
self.step = self.step + 1
self.t = 0.0
self.t = 0.0
end
-- Mark as done when all fade tiles have been shown/hidden.
if self.step > 175 then
self.done = true
-- Execute the callback if any.
if self.callback ~= nil then
self.callback()
self.callback = nil
@@ -69,23 +75,32 @@ end
function Fader:draw()
local c = 1
-- Set the render color to black.
love.graphics.setColor(0, 0, 0)
-- For every fader tile.
for i = 0, 16 do
for j = 0, 10 do
-- Check if this is a fade in or fade out.
if not self.reverse then
-- If it's a fade out then draw black tiles until c.
if c <= self.step then
love.graphics.rectangle('fill', 20 * i, 20 * j, 20, 20)
end
else
if c <= self.step then else
-- If it's a fade in then draw black tiles with indices higher than c.
if c > self.step then
love.graphics.rectangle('fill', 20 * i, 20 * j, 20, 20)
end
end
-- Advance to the next tile to show/hide.
c = c + 1
end
end
-- Reset the render color to white.
love.graphics.setColor()
end

56
src/graphics/sprite.lua Normal file
View File

@@ -0,0 +1,56 @@
------------------------------------------------------------------------------
-- Imports
------------------------------------------------------------------------------
local love = require 'love'
local make_class = require 'src.utils.classes'
local Asset = require 'src.utils.asset'
local Drawable = require 'src.graphics.drawable'
------------------------------------------------------------------------------
-- Class definitions
------------------------------------------------------------------------------
local Sprite = make_class(Drawable, Asset)
------------------------------------------------------------------------------
-- Class methods
------------------------------------------------------------------------------
function Sprite:_init(file_name, x, y)
Asset._init(self, file_name)
self.x = (x ~= nil and x) or 0
self.y = (y ~= nil and y) or 0
end
function Sprite:load()
if not self:is_loaded() then
self.sprite = love.graphics.newImage(self.file_name)
end
end
function Sprite:unload()
self.sprite = nil
end
function Sprite:is_loaded()
return self.sprite ~= nil
end
function Sprite:draw()
if self.sprite ~= nil then
love.graphics.draw(self.sprite, self.x, self.y)
end
end
------------------------------------------------------------------------------
-- Module return
------------------------------------------------------------------------------
return Sprite

View File

@@ -3,13 +3,15 @@
------------------------------------------------------------------------------
local make_class = require 'src.utils.classes'
local assets = require 'src.utils.asstmngr'
local Drawable = require 'src.graphics.drawable'
------------------------------------------------------------------------------
-- Class definitions
------------------------------------------------------------------------------
local GameState = make_class()
local GameState = make_class(Drawable)
------------------------------------------------------------------------------
@@ -17,9 +19,15 @@ local GameState = make_class()
------------------------------------------------------------------------------
function GameState:_init(name, index)
self.name = name
self.index = index
self.valid = false
Drawable._init(self)
self.name = name
self.index = index
self.next_state = self.index
end
function GameState:update(_)
return self.next_state
end
@@ -27,42 +35,11 @@ function GameState:load()
end
function GameState:update(_)
return self.index
end
function GameState:draw()
end
function GameState:unload()
assets:unload(self.name)
end
function GameState:keypressed(_, _ , _)
end
function GameState:textinput(_)
end
function GameState:keyreleased(_, _)
end
function GameState:mousemoved(_, _, _, _)
end
function GameState:mousepressed(_, _, _)
end
function GameState:mousereleased(_, _, _)
end
------------------------------------------------------------------------------
-- Module return
------------------------------------------------------------------------------

View File

@@ -2,11 +2,11 @@
-- Imports
------------------------------------------------------------------------------
local love = require 'love'
local make_class = require 'src.utils.classes'
local GameState = require 'src.gstates.gstate'
local Fader = require 'src.utils.fader'
local make_class = require 'src.utils.classes'
local GameState = require 'src.gstates.gstate'
local Sprite = require 'src.graphics.sprite'
local Fader = require 'src.graphics.fader'
local SoundEffect = require 'src.sound.sfx'
------------------------------------------------------------------------------
-- Class definitions
@@ -21,22 +21,31 @@ local Intro = make_class(GameState)
function Intro:_init(name, index)
GameState._init(self, name, index)
self.skip = false
self.fade = Fader()
self.skip = false
self.fade = Fader()
self.stage = 0
self.timer = 0
self.splash = Sprite('imgs/splash.png')
self.author = Sprite('imgs/wally.png')
self.title = Sprite('imgs/title.png')
self.splash_snd = SoundEffect('snd/jachiev.wav')
self.author_snd = SoundEffect('snd/alang.wav')
self.title_snd = SoundEffect('snd/ablhole.wav')
end
function Intro:load()
self.splash = love.graphics.newImage('imgs/splash.png')
self.author = love.graphics.newImage('imgs/wally.png')
self.title = love.graphics.newImage('imgs/title.png')
self.splash_snd = love.audio.newSource('snd/jachiev.wav')
self.author_snd = love.audio.newSource('snd/alang.wav')
self.title_snd = love.audio.newSource('snd/ablhole.wav')
self.valid = true
-- Load all assets.
self.splash:load()
self.author:load()
self.title:load()
self.splash_snd:load()
self.author_snd:load()
self.title_snd:load()
-- Play the initial sound effect.
self.splash_snd:play()
end
@@ -44,21 +53,25 @@ end
function Intro:update(dt)
local total_time
-- Set time to wait for each intro stage.
if self.stage == 0 then
total_time = 4.5
total_time = 6.5
elseif self.stage == 1 then
total_time = 3.0
total_time = 5.0
else
total_time = 8.0
end
-- Start counting the stage time when the fade in is done.
if self.fade.done then
self.timer = self.timer + dt
end
-- Check if it's time to change stage.
if self.timer >= total_time then
self.fade:fade_out(
function ()
-- Change the stage and play the corresponding sound.
self.stage = self.stage + 1
if self.stage == 1 then
@@ -67,41 +80,47 @@ function Intro:update(dt)
self.title_snd:play()
end
-- Request a fade in.
self.fade:fade_in()
end
)
-- Reset the stage timer.
self.timer = 0.0
end
-- Update the fader.
self.fade:update(dt)
-- Move on to the next game state if the user skipped the intro or all stages are complete.
if not self.skip and self.stage < 3 then return self.index else return 2 end
end
function Intro:draw()
if self.valid then
if self.stage == 0 then love.graphics.draw(self.splash, 0, 0)
elseif self.stage == 1 then love.graphics.draw(self.author, 0, 0)
elseif self.stage == 2 then love.graphics.draw(self.title, 0, 0) end
end
-- If there is data to draw, then draw the current stage's backdrop.
if self.stage == 0 then self.splash:draw()
elseif self.stage == 1 then self.author:draw()
elseif self.stage == 2 then self.title:draw() end
-- Draw the fader if needed.
self.fade:draw()
end
function Intro:unload()
self.splash = nil
self.author = nil
self.title = nil
self.splash_snd = nil
self.author_snd = nil
self.title_snd = nil
self.valid = false
-- Null all assets so they can be claimed by the GC.
self.splash:unload()
self.author:unload()
self.title:unload()
self.splash_snd:unload()
self.author_snd:unload()
self.title_snd:unload()
end
function Intro:keypressed(_)
-- Skip the intro on any key press.
self.skip = true
end

185
src/gstates/menu.lua Normal file
View File

@@ -0,0 +1,185 @@
------------------------------------------------------------------------------
-- Imports
------------------------------------------------------------------------------
local love = require 'love'
local assets = require 'src.utils.asstmngr'
local sound_manager = require 'src.sound.sndmngr'
local dialog_manager = require 'src.ui.dlgmngr'
local make_class = require 'src.utils.classes'
local constants = require 'src.gstates.menus.const'
local GameState = require 'src.gstates.gstate'
local MainMenu = require 'src.gstates.menus.mainmenu'
local OptionsMenu = require 'src.gstates.menus.options'
local Fader = require 'src.graphics.fader'
local Cursor = require 'src.ui.cursor'
local Font = require 'src.ui.font'
local SoundEffect = require 'src.sound.sfx'
------------------------------------------------------------------------------
-- Class definitions
------------------------------------------------------------------------------
local Menu = make_class(GameState)
------------------------------------------------------------------------------
-- Class methods
------------------------------------------------------------------------------
function Menu:_init(name, index)
GameState._init(self, name, index)
self.fade = Fader()
self.current_menu = constants.MAIN_MENU
self.next_menu = constants.MAIN_MENU
self.all_loaded = false
-- Create sprites and buttons.
self.btn_font = Font('fonts/Concrete.ttf')
self.title_font = Font('fonts/BBrick.ttf', 35)
-- Create a mouse cursor object at the current mouse position.
local mx, my = love.mouse.getPosition()
self.cursor = Cursor(mx, my)
-- Create sound effects.
self.bgm = SoundEffect('bgm/eskisky.wav')
-- Create the sub-menus.
self.menus = { }
self.menus[constants.MAIN_MENU] = MainMenu(self, self.title_font, self.btn_font)
self.menus[constants.OPTIONS_MENU] = OptionsMenu(self, self.title_font, self.btn_font)
-- Register all assets.
assets:register(self.name, self.btn_font)
assets:register(self.name, self.title_font)
assets:register(self.name, self.cursor)
assets:register(self.name, self.bgm)
end
function Menu:update(dt)
if not self.bgm:isPlaying() then sound_manager:play_music(self.bgm) end
if self.all_loaded then
self.menus[self.current_menu]:update(dt)
dialog_manager:update(dt)
-- If the game state changed then trigger a fade out.
if self.next_menu ~= self.current_menu and self.fade.done then
self.fade:fade_out()
end
self.fade:update(dt)
-- Update the game state.
if self.menus[self.next_menu] == nil then
error(string.format('%s: switched to unknown menu', self.next_menu))
else
-- If the new state exists then unload it's data and set the new state.
if self.next_menu ~= self.current_menu and self.fade.done then
self.current_menu = self.next_menu
self.fade:fade_in()
end
end
else
local done = true
assets:update(self.name)
for _, v in pairs(self.menus) do
assets:update(v.name)
done = done and assets:done(v.name)
end
self.all_loaded = assets:done(self.name) and done
end
return self.next_state
end
function Menu:draw()
if self.all_loaded then
self.menus[self.current_menu]:draw()
dialog_manager:draw()
self.cursor:draw()
self.fade:draw()
else
assets:draw()
end
end
function Menu:keypressed(key, code, isrepeat)
-- Send events to the active game state if there is no fade active.
if Fade.done then
if dialog_manager:empty() then
self.menus[self.current_menu]:keypressed(key, code, isrepeat)
else
dialog_manager:keypressed(key, code, isrepeat)
end
end
end
function Menu:keyreleased(key, code)
-- Send events to the active game state if there is no fade active.
if Fade.done then
if dialog_manager:empty() then
self.menus[self.current_menu]:keyreleased(key, code)
else
dialog_manager:keyreleased(key, code)
end
end
end
function Menu:textinput(text)
-- Send events to the active game state if there is no fade active.
if Fade.done then
if dialog_manager:empty() then
self.menus[self.current_menu]:textinput(text)
else
dialog_manager:textinput(text)
end
end
end
function Menu:mousemoved(x, y, dx, dy)
if dialog_manager:empty() then
self.menus[self.current_menu]:mousemoved(x, y, dx, dy)
else
dialog_manager:mousemoved(x, y, dx, dy)
end
self.cursor:mousemoved(x, y, dx, dy)
end
function Menu:mousepressed(x, y, btn)
if self.fade.done then
if dialog_manager:empty() then
self.menus[self.current_menu]:mousepressed(x, y, btn)
else
dialog_manager:mousepressed(x, y, btn)
end
end
end
function Menu:mousereleased(x, y, btn)
if self.fade.done then
if dialog_manager:empty() then
self.menus[self.current_menu]:mousereleased(x, y, btn)
else
dialog_manager:mousereleased(x, y, btn)
end
end
end
------------------------------------------------------------------------------
-- Module return
------------------------------------------------------------------------------
return Menu

View File

@@ -0,0 +1,75 @@
------------------------------------------------------------------------------
-- Imports
------------------------------------------------------------------------------
local love = require 'love'
local assets = require 'src.utils.asstmngr'
local make_class = require 'src.utils.classes'
local GameState = require 'src.gstates.gstate'
local Sprite = require 'src.graphics.sprite'
local VBox = require 'src.ui.vbox'
------------------------------------------------------------------------------
-- Class definitions
------------------------------------------------------------------------------
local BaseMenu = make_class(GameState)
------------------------------------------------------------------------------
-- Class methods
------------------------------------------------------------------------------
function BaseMenu:_init(parent, name, index, bckg)
GameState._init(self, name, index)
self.parent = parent
-- Create background sprite and container.
self.background = Sprite(bckg)
self.container = VBox(15, 5, love.graphics.getWidth() - 15)
-- Register the background and container into the asset manager.
assets:register(self.name, self.background)
assets:register(self.name, self.container)
end
function BaseMenu:mousemoved(x, y, dx, dy)
self.container:mousemoved(x, y, dx, dy)
end
function BaseMenu:mousepressed(x, y, btn)
self.container:mousepressed(x, y, btn)
end
function BaseMenu:mousereleased(x, y, btn)
self.container:mousereleased(x, y, btn)
end
function BaseMenu:update()
if not assets:done(self.name) then
assets:update(self.name)
end
end
function BaseMenu:draw()
if assets:done(self.name) then
self.background:draw()
self.container:draw()
else
assets:draw()
end
end
------------------------------------------------------------------------------
-- Module return
------------------------------------------------------------------------------
return BaseMenu

View File

@@ -0,0 +1,17 @@
------------------------------------------------------------------------------
-- Constants
------------------------------------------------------------------------------
local constants = {
MAIN_MENU = 1,
OPTIONS_MENU = 2,
STORY = 3,
LOAD_MENU = 4,
}
------------------------------------------------------------------------------
-- Module return
------------------------------------------------------------------------------
return constants

View File

@@ -0,0 +1,41 @@
------------------------------------------------------------------------------
-- Imports
------------------------------------------------------------------------------
local make_class = require 'src.utils.classes'
local constants = require 'src.gstates.menus.const'
local BaseMenu = require 'src.gstates.menus.base'
local Color = require 'src.utils.color'
local Label = require 'src.ui.label'
local TextButton = require 'src.ui.textbtn'
------------------------------------------------------------------------------
-- Class definitions
------------------------------------------------------------------------------
local MainMenu = make_class(BaseMenu)
------------------------------------------------------------------------------
-- Main Menu Class methods
------------------------------------------------------------------------------
function MainMenu:_init(parent, title_font, button_font)
BaseMenu._init(self, parent, "Main Menu", constants.MAIN_MENU, 'imgs/concb03.png')
-- Create UI elements of the main menu.
self.container:add(Label(nil, nil, 'LoveDOS', title_font, Color(215, 0, 0), true))
self.container:add(TextButton('New Game', button_font))
self.container:add(TextButton('Load Game', button_font))
self.container:add(TextButton('Options', button_font, nil, nil, function() parent.next_menu = constants.OPTIONS_MENU end))
self.container:add(TextButton('Help & Story', button_font))
self.container:add(TextButton('Quit', button_font, nil, nil, function() parent.next_state = -1 end))
end
------------------------------------------------------------------------------
-- Module return
------------------------------------------------------------------------------
return MainMenu

View File

@@ -0,0 +1,178 @@
------------------------------------------------------------------------------
-- Imports
------------------------------------------------------------------------------
local make_class = require 'src.utils.classes'
local constants = require 'src.gstates.menus.const'
local settings = require 'src.utils.settings'
local sound_manager = require 'src.sound.sndmngr'
local assets = require 'src.utils.asstmngr'
local BaseMenu = require 'src.gstates.menus.base'
local Color = require 'src.utils.color'
local HBox = require 'src.ui.hbox'
local Label = require 'src.ui.label'
local TextButton = require 'src.ui.textbtn'
local TextInput = require 'src.ui.textinpt'
local Checkbox = require 'src.ui.chkbox'
local Bar = require 'src.ui.bar'
local Dialog = require 'src.ui.dialog'
local Font = require 'src.ui.font'
------------------------------------------------------------------------------
-- Class definitions
------------------------------------------------------------------------------
local OptionsMenu = make_class(BaseMenu)
------------------------------------------------------------------------------
-- Class methods
------------------------------------------------------------------------------
function OptionsMenu:_init(parent, title_font, button_font)
BaseMenu._init(self, parent, "Options Menu", constants.OPTIONS_MENU, 'imgs/cpu.png')
self.subtitle_font = Font('fonts/BBrick.ttf', 25)
-- Create UI elements of the menu.
self.container:add(Label(nil, nil, 'Options', title_font, Color(215, 0, 0), true))
-- Permadeath checkbox.
local box = HBox()
box:add(Label(nil, nil, 'Enable Permadeath: ', button_font))
box:add(Checkbox(
function()
settings.permadeath = not settings.permadeath
end,
function()
return settings.permadeath
end,
17,
15))
self.container:add(box)
-- Control binding dialog.
local dlg = Dialog('imgs/tchcmp1.png', nil, 15, nil, nil, button_font)
dlg:add(Label(nil, nil, 'Set Controls', self.subtitle_font, Color(215, 0, 0), true))
box = HBox()
box.spacing = 30
-- text, font, x, y, callback, value, float, base_col, sel_color, press_col
box:add(TextInput('Step Forw.',
button_font,
nil, nil,
function(v) settings.forward = v end,
function() return settings.forward end))
box:add(TextInput('Step Back',
button_font,
nil, nil,
function(v) settings.backward = v end,
function() return settings.backward end))
dlg:add(box)
box = HBox()
box.spacing = 40
box:add(TextInput('Step Left',
button_font,
nil, nil,
function(v) settings.stepleft = v end,
function() return settings.stepleft end))
box:add(TextInput('Step Right',
button_font,
nil, nil,
function(v) settings.stepright = v end,
function() return settings.stepright end))
dlg:add(box)
box = HBox()
box.spacing = 42
box:add(TextInput('Turn Left',
button_font,
nil, nil,
function(v) settings.turnleft = v end,
function() return settings.turnleft end))
box:add(TextInput('Turn Right',
button_font,
nil, nil,
function(v) settings.turnright = v end,
function() return settings.turnright end))
dlg:add(box)
box = HBox()
box:add(TextButton(
'Set Controls',
button_font,
nil,
nil,
function()
dlg:show()
end))
self.container:add(box)
-- Sound volume bar.
box = HBox()
box:add(Label(nil, nil, 'Sound Volume: ', button_font))
box:add(Bar(
function(v)
settings.soundvol = math.ceil(v)
end,
function()
return settings.soundvol
end,
0, 100,
170, 15))
self.container:add(box)
-- Music volume bar.
box = HBox()
box.spacing = 13
box:add(Label(nil, nil, 'Music Volume: ', button_font))
box:add(Bar(
function(v)
settings.musicvol = math.ceil(v)
sound_manager:update_volumes()
end,
function()
return settings.musicvol
end,
0, 100,
170, 15))
self.container:add(box)
-- Back button.
box = HBox()
box:add(TextButton(
'Go Back',
button_font,
nil,
nil,
function()
settings:save_settings()
parent.next_menu = constants.MAIN_MENU
end))
self.container:add(box)
-- Register the dialog with the asset manager.
assets:register(self.name, dlg)
assets:register(self.name, self.subtitle_font)
end
------------------------------------------------------------------------------
-- Module return
------------------------------------------------------------------------------
return OptionsMenu

113
src/sound/sfx.lua Normal file
View File

@@ -0,0 +1,113 @@
------------------------------------------------------------------------------
-- Imports
------------------------------------------------------------------------------
local love = require 'love'
local make_class = require 'src.utils.classes'
local Asset = require 'src.utils.asset'
------------------------------------------------------------------------------
-- Class definitions
------------------------------------------------------------------------------
-- SoundEffect is a simple wrapper around Löve's Source class to allow for
-- easy loading/unloading and automatically checking if a sound effect is valid
-- before using it.
local SoundEffect = make_class(Asset)
------------------------------------------------------------------------------
-- Class methods
------------------------------------------------------------------------------
function SoundEffect:_init(file_name)
Asset._init(self, file_name)
end
function SoundEffect:load()
if not self:is_loaded() then
self.sfx = love.audio.newSource(self.file_name)
end
end
function SoundEffect:unload()
if self:isPlaying() then self:stop() end
self.sfx = nil
end
function SoundEffect:is_loaded()
return self.sfx ~= nil
end
function SoundEffect:setVolume(volume)
if self.sfx ~= nil then self.sfx:setVolume(volume) end
end
function SoundEffect:setPitch(pitch)
if self.sfx ~= nil then self.sfx:setPitch(pitch) end
end
function SoundEffect:setLooping(enable)
if self.sfx ~= nil then self.sfx:setLooping(enable) end
end
function SoundEffect:getDuration()
if self.sfx ~= nil then
return self.sfx:getDuration()
else
return nil
end
end
function SoundEffect:isPlaying()
return self.sfx ~= nil and self.sfx:isPlaying()
end
function SoundEffect:isPaused()
return self.sfx ~= nil and self.sfx:isPaused()
end
function SoundEffect:isStopped()
return self.sfx ~= nil and self.sfx:isStopped()
end
function SoundEffect:tell()
if self.sfx ~= nil then
return self.sfx:tell()
else
return nil
end
end
function SoundEffect:play()
if self.sfx ~= nil then self.sfx:play() end
end
function SoundEffect:pause()
if self.sfx ~= nil then self.sfx:pause() end
end
function SoundEffect:stop()
if self.sfx ~= nil then self.sfx:stop() end
end
------------------------------------------------------------------------------
-- Module return
------------------------------------------------------------------------------
return SoundEffect

57
src/sound/sndmngr.lua Normal file
View File

@@ -0,0 +1,57 @@
------------------------------------------------------------------------------
-- Imports
------------------------------------------------------------------------------
local make_class = require 'src.utils.classes'
local settings = require 'src.utils.settings'
------------------------------------------------------------------------------
-- Class definitions
------------------------------------------------------------------------------
local SoundManager = make_class()
------------------------------------------------------------------------------
-- Class methods
------------------------------------------------------------------------------
function SoundManager:_init()
self.active_bgm = nil
end
function SoundManager:play_sound(sfx)
local vol = math.max(math.min(settings.soundvol / 100, 1.0), 0.0)
sfx:setVolume(vol)
sfx:play()
end
function SoundManager:play_music(bgm)
local vol = math.max(math.min(settings.musicvol / 100, 1.0), 0.0)
if self.active_bgm ~= nil then
self.active_bgm:stop()
end
self.active_bgm = bgm
self.active_bgm:setVolume(vol)
self.active_bgm:play()
end
function SoundManager:update_volumes()
if self.active_bgm ~= nil then
local vol = math.max(math.min(settings.musicvol / 100, 1.0), 0.0)
self.active_bgm:setVolume(vol)
end
end
------------------------------------------------------------------------------
-- Module return
------------------------------------------------------------------------------
return SoundManager()

View File

@@ -3,6 +3,7 @@
------------------------------------------------------------------------------
local Intro = require 'src.gstates.intro'
local Menu = require 'src.gstates.menu'
------------------------------------------------------------------------------
@@ -12,6 +13,7 @@ local Intro = require 'src.gstates.intro'
-- Table of all valid game states.
local states = {
Intro('intro', 1),
Menu('menu', 2),
}

121
src/ui/bar.lua Normal file
View File

@@ -0,0 +1,121 @@
------------------------------------------------------------------------------
-- Imports
------------------------------------------------------------------------------
local love = require 'love'
local make_class = require 'src.utils.classes'
local collisions = require 'src.utils.colls'
local UIElement = require 'src.ui.element'
local Color = require 'src.utils.color'
------------------------------------------------------------------------------
-- Class definitions
------------------------------------------------------------------------------
local Bar = make_class(UIElement)
------------------------------------------------------------------------------
-- Class methods
------------------------------------------------------------------------------
function Bar:_init(callback, val_fn, min, max, w, h, color, sel_color, press_col, float, x, y)
UIElement._init(self, x, y, float, false)
self.callback = callback
self.val_fn = val_fn
self.min = min
self.max = max
self.w = w
self.h = h
self.base_col = color ~= nil and color or Color(255, 255, 255)
self.sel_color = sel_color ~= nil and sel_color or Color(215, 0, 0)
self.press_col = press_col ~= nil and press_col or Color(99, 99, 139)
self.disbl_col = Color(95, 63, 75)
end
function Bar:set_dimensions()
self.w = self.w
self.h = self.h
end
function Bar:draw()
local color = self.callback == nil and self.disbl_col or ((self.pressed and self.press_col) or ((self.selected and self.sel_color) or self.base_col))
-- Convert the variable's value to the [0, 1] space.
local v = (self.val_fn() + math.abs(self.min)) / (self.max + math.abs(self.min))
love.graphics.setColor(color.r, color.g, color.b)
-- Render the bar's outline.
love.graphics.rectangle('line', self.x, self.y, self.w, self.h)
-- Render the bar's content using the [0, 1] value as a proportion of the bar's width.
love.graphics.rectangle('fill', self.x, self.y, self.w * v, self.h)
love.graphics.setColor()
end
function Bar:_mouse2bar(x)
-- Take the x coord to the [0, 1] space, with clamping.
local v = math.max(math.min(((x - self.x) / self.w), 1.0), 0.0)
-- Convert the value to a horizontal bar coordinate and return it.
return (v * (self.max + math.abs(self.min))) - self.min
end
function Bar:mousemoved(x, y)
-- Select if the pointer is inside the button's bounding rect.
self.selected = collisions.point_in_square(x, y, self.x, self.y, self.w, self.h)
-- If the button was pressed then check if the pointer is no longer inside the button.
if self.pressed then
-- If it's not, then mark the button as "was pressed".
self.pressed = collisions.point_in_square(x, y, self.x, self.y, self.w, self.h)
self.was_pressed = true
self.callback(self:_mouse2bar(x))
end
-- If the button was pressed and the pointer is inside the button right now.
if self.was_pressed and collisions.point_in_square(x, y, self.x, self.y, self.w, self.h) then
-- Then mark the button as pressed again.
self.pressed = true
self.was_pressed = false
self.callback(self:_mouse2bar(x))
end
end
function Bar:mousepressed(x, y, btn)
-- If the pointer was inside the button when it was pressed then mark the button as pressed.
if btn == 0 then
self.pressed = collisions.point_in_square(x, y, self.x, self.y, self.w, self.h)
end
end
function Bar:mousereleased(x, _, btn)
if btn == 0 then
if self.pressed then
-- If the button was pressed then execute the callback if any and unmark as pressed.
if self.callback ~= nil then
self.callback(self:_mouse2bar(x))
end
self.pressed = false
end
-- The button no longer was pressed.
self.was_pressed = false
end
end
------------------------------------------------------------------------------
-- Module return
------------------------------------------------------------------------------
return Bar

90
src/ui/button.lua Normal file
View File

@@ -0,0 +1,90 @@
------------------------------------------------------------------------------
-- Imports
------------------------------------------------------------------------------
local make_class = require 'src.utils.classes'
local collisions = require 'src.utils.colls'
local UIElement = require 'src.ui.element'
------------------------------------------------------------------------------
-- Class definitions
------------------------------------------------------------------------------
---@class Button: UIElement
local Button = make_class(UIElement)
------------------------------------------------------------------------------
-- Class methods
------------------------------------------------------------------------------
---@param x number
---@param y number
---@param callback function
---@param float boolean
function Button:_init(x, y, callback, float)
UIElement._init(self, x, y, float, false)
self.selected = false
self.pressed = false
self.was_pressed = false
self.callback = callback
end
function Button:set_dimensions()
self.w = 0
self.h = 0
end
function Button:mousemoved(x, y)
-- Select if the pointer is inside the button's bounding rect.
self.selected = collisions.point_in_square(x, y, self.x, self.y, self.w, self.h)
-- If the button was pressed then check if the pointer is no longer inside the button.
if self.pressed then
-- If it's not, then mark the button as "was pressed".
self.pressed = collisions.point_in_square(x, y, self.x, self.y, self.w, self.h)
self.was_pressed = true
end
-- If the button was pressed and the pointer is inside the button right now.
if self.was_pressed and collisions.point_in_square(x, y, self.x, self.y, self.w, self.h) then
-- Then mark the button as pressed again.
self.pressed = true
self.was_pressed = false
end
end
function Button:mousepressed(x, y, btn)
-- If the pointer was inside the button when it was pressed then mark the button as pressed.
if btn == 0 then
self.pressed = collisions.point_in_square(x, y, self.x, self.y, self.w, self.h)
end
end
function Button:mousereleased(_, _, btn)
if btn == 0 then
if self.pressed then
-- If the button was pressed then execute the callback if any and unmark as pressed.
if self.callback ~= nil then
self.callback()
end
self.pressed = false
end
-- The button no longer was pressed.
self.was_pressed = false
end
end
------------------------------------------------------------------------------
-- Module return
------------------------------------------------------------------------------
return Button

55
src/ui/chkbox.lua Normal file
View File

@@ -0,0 +1,55 @@
------------------------------------------------------------------------------
-- Imports
------------------------------------------------------------------------------
local love = require 'love'
local make_class = require 'src.utils.classes'
local Button = require 'src.ui.button'
local Color = require 'src.utils.color'
------------------------------------------------------------------------------
-- Class definitions
------------------------------------------------------------------------------
local Checkbox = make_class(Button)
------------------------------------------------------------------------------
-- Class methods
------------------------------------------------------------------------------
function Checkbox:_init(callback, value, w, h, color, sel_color, press_col, float, x, y)
Button._init(self, x, y, callback, float)
self.value = value
self.w = w ~= nil and w or 20
self.h = h ~= nil and h or 20
self.base_col = color ~= nil and color or Color(255, 255, 255)
self.sel_color = sel_color ~= nil and sel_color or Color(215, 0, 0)
self.press_col = press_col ~= nil and press_col or Color(99, 99, 139)
self.disbl_col = Color(95, 63, 75)
end
function Checkbox:set_dimensions()
self.w = self.w
self.h = self.h
end
function Checkbox:draw()
local color = self.callback == nil and self.disbl_col or ((self.pressed and self.press_col) or ((self.selected and self.sel_color) or self.base_col))
local mode = nil
if self.value() then mode = 'fill' else mode = 'line' end
love.graphics.setColor(color.r, color.g, color.b)
love.graphics.rectangle(mode, self.x, self.y, self.w, self.h)
love.graphics.setColor()
end
------------------------------------------------------------------------------
-- Module return
------------------------------------------------------------------------------
return Checkbox

35
src/ui/cursor.lua Normal file
View File

@@ -0,0 +1,35 @@
------------------------------------------------------------------------------
-- Imports
------------------------------------------------------------------------------
local make_class = require 'src.utils.classes'
local Sprite = require 'src.graphics.sprite'
------------------------------------------------------------------------------
-- Class definitions
------------------------------------------------------------------------------
local Cursor = make_class(Sprite)
------------------------------------------------------------------------------
-- Class methods
------------------------------------------------------------------------------
function Cursor:_init(x, y)
Sprite._init(self, 'imgs/pointer.png', x, y)
end
function Cursor:mousemoved(x, y)
self.x = x
self.y = y
end
------------------------------------------------------------------------------
-- Module return
------------------------------------------------------------------------------
return Cursor

117
src/ui/dialog.lua Normal file
View File

@@ -0,0 +1,117 @@
------------------------------------------------------------------------------
-- Imports
------------------------------------------------------------------------------
local love = require 'love'
local make_class = require 'src.utils.classes'
local dialog_manager = require 'src.ui.dlgmngr'
local VBox = require 'src.ui.vbox'
local Sprite = require 'src.graphics.sprite'
local TextButton = require 'src.ui.textbtn'
local HBox = require 'src.ui.hbox'
------------------------------------------------------------------------------
-- Class definitions
------------------------------------------------------------------------------
local Dialog = make_class(VBox)
------------------------------------------------------------------------------
-- Class methods
------------------------------------------------------------------------------
function Dialog:_init(bckg, spacing, padding, close_btn, accept_btn, font)
VBox._init(self, nil, nil, nil, nil, spacing, false)
self.spacing = spacing ~= nil and spacing or 10
self.padding = padding ~= nil and padding or 15
self.background = Sprite(bckg)
self.shown = false
local close = nil
if close_btn ~= nil then
close = close_btn
else
close = TextButton(
'Close',
font,
nil,
nil,
function()
self:hide()
end)
end
local accept = nil
if accept_btn ~= nil then accept = accept_btn end
-- Create a container for the close and accept button and add them.
local box = HBox(nil, nil, nil, nil, 10, true)
box:add(close)
if accept ~= nil then box:add(accept) end
self.elements[9001] = box
end
function Dialog:add(element)
table.insert(self.elements, element)
end
function Dialog:load()
self.background:load()
self:set_dimensions()
VBox.load(self)
end
function Dialog:set_dimensions()
self.w = self.background.sprite:getWidth()
self.h = self.background.sprite:getHeight()
-- Center the dialog on the screen.
self.x = (320 - self.w) / 2
self.y = (200 - self.h) / 2
self.background.x = self.x
self.background.y = self.y
-- Add padding.
self.x = self.x + self.padding
self.y = self.y + self.padding / 2
end
function Dialog:draw()
love.graphics.setColor(0, 0, 0)
love.graphics.rectangle('line', self.x - self.padding - 1, self.y - (self.padding / 2) - 1, self.w + 2, self.h + 2)
love.graphics.setColor()
self.background:draw()
VBox.draw(self)
end
function Dialog:show()
if not self.shown then
dialog_manager:add(self)
self.shown = true
end
end
function Dialog:hide()
dialog_manager:remove(self)
self.shown = false
end
------------------------------------------------------------------------------
-- Module return
------------------------------------------------------------------------------
return Dialog

104
src/ui/dlgmngr.lua Normal file
View File

@@ -0,0 +1,104 @@
------------------------------------------------------------------------------
-- Imports
------------------------------------------------------------------------------
local make_class = require 'src.utils.classes'
local Drawable = require 'src.graphics.drawable'
------------------------------------------------------------------------------
-- Class definitions
------------------------------------------------------------------------------
local DialogManager = make_class(Drawable)
------------------------------------------------------------------------------
-- Class methods
------------------------------------------------------------------------------
function DialogManager:_init()
Drawable._init(self)
self.dialogs = {}
self.count = 0
end
function DialogManager:add(dialog)
self.dialogs[dialog] = true
self.count = self.count + 1
end
function DialogManager:remove(dialog)
self.dialogs[dialog] = nil
self.count = math.max(self.count - 1, 0)
end
function DialogManager:empty()
return self.count == 0
end
function DialogManager:update(dt)
for d, _ in pairs(self.dialogs) do
d:update(dt)
end
end
function DialogManager:draw()
for d, _ in pairs(self.dialogs) do
d:draw()
end
end
function DialogManager:keypressed(key, code, isrepeat)
for d, _ in pairs(self.dialogs) do
d:keypressed(key, code, isrepeat)
end
end
function DialogManager:textinput(text)
for d, _ in pairs(self.dialogs) do
d:textinput(text)
end
end
function DialogManager:keyreleased(key, code)
for d, _ in pairs(self.dialogs) do
d:keyreleased(key, code)
end
end
function DialogManager:mousemoved(x, y, dx, dy)
for d, _ in pairs(self.dialogs) do
d:mousemoved(x, y, dx, dy)
end
end
function DialogManager:mousepressed(x, y, btn)
for d, _ in pairs(self.dialogs) do
d:mousepressed(x, y, btn)
end
end
function DialogManager:mousereleased(x, y, btn)
for d, _ in pairs(self.dialogs) do
d:mousereleased(x, y, btn)
end
end
------------------------------------------------------------------------------
-- Module return
------------------------------------------------------------------------------
return DialogManager()

84
src/ui/element.lua Normal file
View File

@@ -0,0 +1,84 @@
------------------------------------------------------------------------------
-- Imports
------------------------------------------------------------------------------
local make_class = require 'src.utils.classes'
local Asset = require 'src.utils.asset'
local Drawable = require 'src.graphics.drawable'
------------------------------------------------------------------------------
-- Class definitions
------------------------------------------------------------------------------
local focused_element = nil
------------------------------------------------------------------------------
-- Class definitions
------------------------------------------------------------------------------
---@class UIElement: Drawable, Asset
local UIElement = make_class(Drawable, Asset)
------------------------------------------------------------------------------
-- Class methods
------------------------------------------------------------------------------
---@param x integer
---@param y integer
---@param float boolean
---@param focusable boolean
function UIElement:_init(x, y, float, focusable)
Asset._init(self)
self.x = x
self.y = y
self.w = nil
self.h = nil
self.float_right = float and float or false
self.focusable = focusable ~= nil and focusable or false
end
function UIElement:set_dimensions()
self.w = 0
self.h = 0
end
function UIElement:load()
end
function UIElement:unload()
end
function UIElement:is_loaded()
return true
end
function UIElement:focus()
if self.focusable then
focused_element = self
end
end
function UIElement:blur()
if self.focusable and focused_element == self then
focused_element = nil
end
end
function UIElement:is_focused()
return self.focusable and focused_element == self
end
------------------------------------------------------------------------------
-- Module return
------------------------------------------------------------------------------
return UIElement

73
src/ui/font.lua Normal file
View File

@@ -0,0 +1,73 @@
------------------------------------------------------------------------------
-- Imports
------------------------------------------------------------------------------
local love = require 'love'
local make_class = require 'src.utils.classes'
local Asset = require 'src.utils.asset'
------------------------------------------------------------------------------
-- Class definitions
------------------------------------------------------------------------------
---@class Font: Asset
-- Font is a simple wrapper around Löve's own Font class to allow for
-- easy loading/unloading and automatically checking if it's is valid
-- before using it.
local Font = make_class(Asset)
------------------------------------------------------------------------------
-- Class methods
------------------------------------------------------------------------------
---@param file_name string
---@param size number
function Font:_init(file_name, size)
self.file_name = string.format('assets/%s', file_name)
self.size = (size ~= nil and size) or 20
end
function Font:load()
if not self:is_loaded() then
self.f = love.graphics.newFont(self.file_name, self.size)
end
end
function Font:unload()
self.f = nil
end
function Font:is_loaded()
return self.f ~= nil
end
function Font:set()
if self.f ~= nil then love.graphics.setFont(self.f) end
end
function Font:unset()
love.graphics.setFont()
end
function Font:get_width(text)
if self.f ~= nil then return self.f:getWidth(text) else return nil end
end
function Font:get_height(text)
if self.f ~= nil then return self.f:getHeight(text) else return nil end
end
------------------------------------------------------------------------------
-- Module return
------------------------------------------------------------------------------
return Font

63
src/ui/hbox.lua Normal file
View File

@@ -0,0 +1,63 @@
------------------------------------------------------------------------------
-- Imports
------------------------------------------------------------------------------
local make_class = require 'src.utils.classes'
local Layout = require 'src.ui.layout'
------------------------------------------------------------------------------
-- Class definitions
------------------------------------------------------------------------------
---@class HBox: Layout
local HBox = make_class(Layout)
------------------------------------------------------------------------------
-- Class methods
------------------------------------------------------------------------------
function HBox:_init(x, y, w, h, spacing, float)
Layout._init(self, x, y, w, h, spacing, float)
end
function HBox:load()
Layout.load(self)
local _x = self.x
-- Load required assets if needed and then compute the coordinates of each button.
for _, v in pairs(self.elements) do
v.y = self.y
v.x = _x
_x = _x + v.w + self.spacing
end
end
function HBox:set_dimensions()
if self.w == nil then
self.w = 0
for i, v in pairs(self.elements) do
self.w = self.w + v.w
if i < #self.elements then self.w = self.w + self.spacing end
end
end
if self.h == nil then
self.h = 0
for _, v in pairs(self.elements) do
if v.h > self.h then self.h = v.h end
end
end
end
------------------------------------------------------------------------------
-- Module return
------------------------------------------------------------------------------
return HBox

74
src/ui/label.lua Normal file
View File

@@ -0,0 +1,74 @@
------------------------------------------------------------------------------
-- Imports
------------------------------------------------------------------------------
local love = require 'love'
local make_class = require 'src.utils.classes'
local UIElement = require 'src.ui.element'
local Color = require 'src.utils.color'
------------------------------------------------------------------------------
-- Class definitions
------------------------------------------------------------------------------
---@class Label: UIElement
local Label = make_class(UIElement)
------------------------------------------------------------------------------
-- Class methods
------------------------------------------------------------------------------
---@param x number
---@param y number
---@param text string
---@param font Font
---@param color Color
---@param float boolean
function Label:_init(x, y, text, font, color, float)
UIElement._init(self, x, y, float, false)
self.text = text
self.font = font
self.color = color ~= nil and color or Color(255, 255, 255)
end
function Label:set_dimensions()
self.w = self.font:get_width(self.text)
self.h = self.font:get_height(self.text)
end
function Label:load()
self.font:load()
-- If the label's size has not been computed then do it.
if self.w == nil or self.h == nil then self:set_dimensions() end
end
function Label:unload()
self.font:unload()
end
function Label:is_loaded()
return self.font:is_loaded() and self.w ~= nil and self.h ~= nil
end
function Label:draw()
love.graphics.setColor(self.color.r, self.color.g, self.color.b)
self.font:set()
love.graphics.print(self.text, self.x, self.y)
self.font:unset()
love.graphics.setColor()
end
------------------------------------------------------------------------------
-- Module return
------------------------------------------------------------------------------
return Label

118
src/ui/layout.lua Normal file
View File

@@ -0,0 +1,118 @@
------------------------------------------------------------------------------
-- Imports
------------------------------------------------------------------------------
local make_class = require 'src.utils.classes'
local UIElement = require 'src.ui.element'
------------------------------------------------------------------------------
-- Class definitions
------------------------------------------------------------------------------
---@class Layout: UIElement
local Layout = make_class(UIElement)
------------------------------------------------------------------------------
-- Class methods
------------------------------------------------------------------------------
function Layout:_init(x, y, w, h, spacing, float)
UIElement._init(self, x, y, float, false)
self.x = x ~= nil and x or 0
self.y = y ~= nil and y or 0
self.w = w
self.h = h
self.spacing = spacing ~= nil and spacing or 10
self.elements = {}
end
function Layout:add(element)
table.insert(self.elements, element)
end
function Layout:load()
-- Load required assets if needed and then compute the coordinates of each button.
for _, v in pairs(self.elements) do
if v.is_a[UIElement] then
v:load()
end
end
self:set_dimensions()
end
function Layout:unload()
for _, v in pairs(self.elements) do
if v.is_a[UIElement] then
v:unload()
end
end
end
function Layout:update(dt)
for _, v in pairs(self.elements) do
v:update(dt)
end
end
function Layout:draw()
for _, v in pairs(self.elements) do
v:draw()
end
end
function Layout:keypressed(key, code, isrepeat)
for _, v in pairs(self.elements) do
v:keypressed(key, code, isrepeat)
end
end
function Layout:textinput(text)
for _, v in pairs(self.elements) do
v:textinput(text)
end
end
function Layout:keyreleased(key, code)
for _, v in pairs(self.elements) do
v:keyreleased(key, code)
end
end
function Layout:mousemoved(x, y, dx, dy)
for _, v in pairs(self.elements) do
v:mousemoved(x, y, dx, dy)
end
end
function Layout:mousepressed(x, y, btn)
for _, v in pairs(self.elements) do
v:mousepressed(x, y, btn)
end
end
function Layout:mousereleased(x, y, btn)
for _, v in pairs(self.elements) do
v:mousereleased(x, y, btn)
end
end
------------------------------------------------------------------------------
-- Module return
------------------------------------------------------------------------------
return Layout

74
src/ui/textbtn.lua Normal file
View File

@@ -0,0 +1,74 @@
------------------------------------------------------------------------------
-- Imports
------------------------------------------------------------------------------
local love = require 'love'
local make_class = require 'src.utils.classes'
local Button = require 'src.ui.button'
local Color = require 'src.utils.color'
------------------------------------------------------------------------------
-- Class definitions
------------------------------------------------------------------------------
local TextButton = make_class(Button)
------------------------------------------------------------------------------
-- Class methods
------------------------------------------------------------------------------
function TextButton:_init(text, font, x, y, callback, float, base_col, sel_color, press_col)
Button._init(self, x, y, callback, float)
self.font = font
self.text = text
self.base_col = base_col ~= nil and base_col or Color(255, 255, 255)
self.sel_color = sel_color ~= nil and sel_color or Color(215, 0, 0)
self.press_col = press_col ~= nil and press_col or Color(99, 99, 139)
self.disbl_col = Color(95, 63, 75)
end
function TextButton:load()
if not self.font:is_loaded() then
self.font:load()
end
-- If the label's size has not been computed then do it.
if self.w == nil or self.h == nil then self:set_dimensions() end
end
function TextButton:unload()
self.font:unload()
end
function TextButton:is_loaded()
return self.font:is_loaded() and self.w ~= nil and self.h ~= nil
end
function TextButton:set_dimensions()
self.w = self.font:get_width(self.text)
self.h = self.font:get_height(self.text)
end
function TextButton:draw()
local color = self.callback == nil and self.disbl_col or ((self.pressed and self.press_col) or ((self.selected and self.sel_color) or self.base_col))
love.graphics.setColor(color.r, color.g, color.b)
self.font:set()
love.graphics.print(self.text, self.x, self.y)
self.font:unset()
love.graphics.setColor()
end
------------------------------------------------------------------------------
-- Module return
------------------------------------------------------------------------------
return TextButton

118
src/ui/textinpt.lua Normal file
View File

@@ -0,0 +1,118 @@
------------------------------------------------------------------------------
-- Imports
------------------------------------------------------------------------------
local love = require 'love'
local make_class = require 'src.utils.classes'
local collisions = require 'src.utils.colls'
local UIElement = require 'src.ui.element'
local Color = require 'src.utils.color'
------------------------------------------------------------------------------
-- Class definitions
------------------------------------------------------------------------------
---@class CharInput: UIElement
local TextInput = make_class(UIElement)
------------------------------------------------------------------------------
-- Class methods
------------------------------------------------------------------------------
---@param text string
---@param font Font
---@param x number
---@param y number
---@param callback function
---@param value function
---@param float boolean
---@param base_col Color
---@param sel_color Color
---@param press_col Color
function TextInput:_init(text, font, x, y, callback, value, float, base_col, sel_color, press_col)
UIElement._init(self, x, y, float, true)
self.callback = callback
self.value = value
self.font = font
self.text = text
self.base_col = base_col ~= nil and base_col or Color(255, 255, 255)
self.sel_color = sel_color ~= nil and sel_color or Color(215, 0, 0)
self.press_col = press_col ~= nil and press_col or Color(99, 99, 139)
self.disbl_col = Color(95, 63, 75)
self.selected = false
self.pressed = false
end
function TextInput:load()
self.font:load()
-- If the label's size has not been computed then do it.
if self.w == nil or self.h == nil then self:set_dimensions() end
end
function TextInput:unload()
self.font:unload()
end
function TextInput:is_loaded()
return self.font:is_loaded() and self.w ~= nil and self.h ~= nil
end
function TextInput:set_dimensions()
self.w = self.font:get_width(self.text .. 'm')
self.h = self.font:get_height(self.text)
end
function TextInput:draw()
local color = self.callback == nil and self.disbl_col or (((self.pressed or self:is_focused()) and self.press_col) or ((self.selected and self.sel_color) or self.base_col))
love.graphics.setColor(color.r, color.g, color.b)
self.font:set()
if self:is_focused() then
love.graphics.print(self.text .. ': ?', self.x, self.y)
else
love.graphics.print(self.text .. ': ' .. self.value(), self.x, self.y)
end
self.font:unset()
love.graphics.setColor()
end
function TextInput:mousemoved(x, y)
-- Select if the pointer is inside the button's bounding rect.
self.selected = collisions.point_in_square(x, y, self.x, self.y, self.w, self.h)
end
function TextInput:mousepressed(_, _, btn)
-- If the pointer was inside the button when it was pressed then mark the button as pressed.
if btn == 0 and self.selected then
self:focus()
end
end
function TextInput:keypressed(key)
if self:is_focused() then
if #key == 1 then
if self.callback ~= nil then self.callback(key) end
self:blur()
end
end
end
------------------------------------------------------------------------------
-- Module return
------------------------------------------------------------------------------
return TextInput

66
src/ui/vbox.lua Normal file
View File

@@ -0,0 +1,66 @@
------------------------------------------------------------------------------
-- Imports
------------------------------------------------------------------------------
local make_class = require 'src.utils.classes'
local Layout = require 'src.ui.layout'
------------------------------------------------------------------------------
-- Class definitions
------------------------------------------------------------------------------
---@class VBox: Layout
local VBox = make_class(Layout)
------------------------------------------------------------------------------
-- Class methods
------------------------------------------------------------------------------
function VBox:_init(x, y, w, h, spacing, float)
Layout._init(self, x, y, w, h, spacing, float)
end
function VBox:load()
Layout.load(self)
local _y = self.y
-- Load required assets if needed and then compute the coordinates of each button.
for _, v in pairs(self.elements) do
if v.float_right then
v.x = self.w - v.w
else
v.x = self.x
end
v.y = _y
_y = _y + v.h + self.spacing
end
end
function VBox:set_dimensions()
if self.w == nil then
self.w = 0
for _, v in pairs(self.elements) do
if v.w > self.w then self.w = v.w end
end
end
if self.h == nil then
self.h = 0
for i, v in pairs(self.elements) do
self.h = self.h + v.h
if i < #self.elements then self.h = self.h + self.spacing end
end
end
end
------------------------------------------------------------------------------
-- Module return
------------------------------------------------------------------------------
return VBox

44
src/utils/asset.lua Normal file
View File

@@ -0,0 +1,44 @@
------------------------------------------------------------------------------
-- Imports
------------------------------------------------------------------------------
local make_class = require 'src.utils.classes'
------------------------------------------------------------------------------
-- Class definitions
------------------------------------------------------------------------------
---@class Asset
local Asset = make_class()
------------------------------------------------------------------------------
-- Class methods
------------------------------------------------------------------------------
function Asset:_init(file_name)
self.file_name = string.format('assets/%s', file_name)
end
function Asset:load()
error 'Attempted to load unimplemented asset.'
end
function Asset:unload()
error 'Attempted to unload unimplemented asset.'
end
function Asset:is_loaded()
return false
end
------------------------------------------------------------------------------
-- Module return
------------------------------------------------------------------------------
return Asset

135
src/utils/asstmngr.lua Normal file
View File

@@ -0,0 +1,135 @@
------------------------------------------------------------------------------
-- Imports
------------------------------------------------------------------------------
local love = require 'love'
local make_class = require 'src.utils.classes'
local Drawable = require 'src.graphics.drawable'
local Asset = require 'src.utils.asset'
local Sprite = require 'src.graphics.sprite'
local Font = require 'src.ui.font'
------------------------------------------------------------------------------
-- Class definitions
------------------------------------------------------------------------------
local AssetManager = make_class(Drawable)
------------------------------------------------------------------------------
-- Helper functions
------------------------------------------------------------------------------
local function _load(manager, owner)
if manager.assets[owner] ~= nil then
for k, _ in pairs(manager.assets[owner]) do
if k.is_a ~= nil and k.is_a[Asset] then
k:load()
end
coroutine.yield(false)
end
end
return true
end
------------------------------------------------------------------------------
-- Class methods
------------------------------------------------------------------------------
function AssetManager:_init()
Drawable._init(self)
self.assets = {}
self.bckg = Sprite('imgs/floppy.png')
self.font = Font('fonts/BBrick.ttf', 40)
self.co = nil
self.bckg:load()
self.font:load()
end
function AssetManager:unload(owner)
-- If the owner is known...
if self.assets[owner] ~= nil then
-- Iterate over it's asset set and unload everything.
for k, _ in pairs(self.assets[owner]) do
if k.is_a ~= nil and k.is_a[Asset] then
k:unload()
end
end
end
end
function AssetManager:update(owner)
if self.assets[owner] ~= nil then
if self.assets[owner].co == nil then
self.assets[owner].co = coroutine.create(_load)
end
local errorfree, retval = coroutine.resume(self.assets[owner].co, self, owner)
if errorfree then
self.assets[owner].done = retval
if retval then
self.assets[owner].co = nil
end
else
error(retval)
end
end
end
function AssetManager:draw()
self.bckg:draw()
love.graphics.setColor(195, 147, 123)
self.font:set()
love.graphics.print('Loading...', 5, 150)
self.font:unset()
love.graphics.setColor()
end
function AssetManager:register(owner, asset)
-- If the owner is new, then create an asset set for it.
if self.assets[owner] == nil then
self.assets[owner] = {
done = false,
co = nil,
}
end
-- Store the asset in the set.
self.assets[owner][asset] = true
end
function AssetManager:remove(owner, asset)
-- If the owner is new, then create an asset set for it.
if self.assets[owner] ~= nil then
-- Unload the asset just in case.
if self.assets[owner][asset] ~= nil then
self.assets[owner][asset]:unload()
end
-- Then remove it from the set.
self.assets[owner][asset] = nil
end
end
function AssetManager:done(owner)
return self.assets[owner] ~= nil and self.assets[owner].done
end
------------------------------------------------------------------------------
-- Module return
------------------------------------------------------------------------------
return AssetManager()

View File

@@ -2,15 +2,16 @@
-- Methods
------------------------------------------------------------------------------
-- Single inheritance version of the sample code from
-- http://lua-users.org/wiki/ObjectOrientationTutorial
local function make_class(BaseClass)
--- Code from http://lua-users.org/wiki/ObjectOrientationTutorial
---@param ... table Optional base classes
---@return table cls A class definition
local function make_class(...)
-- "cls" is the new class
local cls = {}
local cls, bases = {}, {...}
-- copy base class contents into the new class
if BaseClass ~= nil then
for k, v in pairs(BaseClass) do
for _, base in ipairs(bases) do
for k, v in pairs(base) do
cls[k] = v
end
end
@@ -19,8 +20,11 @@ local function make_class(BaseClass)
-- so you can do an "instance of" check using my_instance.is_a[MyClass]
cls.__index, cls.is_a = cls, {[cls] = true}
if BaseClass ~= nil then
cls.is_a[BaseClass] = true
for _, base in ipairs(bases) do
for c in pairs(base.is_a) do
cls.is_a[c] = true
end
cls.is_a[base] = true
end
-- the class's __call metamethod

17
src/utils/colls.lua Normal file
View File

@@ -0,0 +1,17 @@
------------------------------------------------------------------------------
-- Module definitions
------------------------------------------------------------------------------
local collisions = {
-- Default setting values.
point_in_square = function(x, y, ox, oy, w, h)
return x >= ox and x <= ox + w and y >= oy and y <= oy + h
end,
}
------------------------------------------------------------------------------
-- Module return
------------------------------------------------------------------------------
return collisions

31
src/utils/color.lua Normal file
View File

@@ -0,0 +1,31 @@
------------------------------------------------------------------------------
-- Imports
------------------------------------------------------------------------------
local make_class = require 'src.utils.classes'
------------------------------------------------------------------------------
-- Class definitions
------------------------------------------------------------------------------
---@class Color
local Color = make_class()
------------------------------------------------------------------------------
-- Class methods
------------------------------------------------------------------------------
function Color:_init(r, g, b)
self.r = r ~= nil and r or 255
self.g = g ~= nil and g or 255
self.b = b ~= nil and b or 255
end
------------------------------------------------------------------------------
-- Module return
------------------------------------------------------------------------------
return Color

View File

@@ -3,6 +3,7 @@
------------------------------------------------------------------------------
-- From https://stackoverflow.com/a/19327160
-- Cargo cult programming ahoy!!
local function magiclines( str )
local pos
pos = 1

View File

@@ -2,8 +2,9 @@
-- Imports
------------------------------------------------------------------------------
local love = require 'love'
local love = require 'love'
local magiclines = require 'src.utils.mgclines'
local make_class = require 'src.utils.classes'
------------------------------------------------------------------------------
@@ -17,29 +18,39 @@ local SETTINGS_PATH = 'settings.ini'
-- Module definitions
------------------------------------------------------------------------------
local settings = {
permadeath=true,
forward='w',
backward='s',
stepleft='a',
stepright='d',
turnleft='q',
turnright='e',
musicvol=75,
soundvol=100,
}
---@class Settings
local Settings = make_class()
------------------------------------------------------------------------------
-- Private module methods
------------------------------------------------------------------------------
function settings:_parse_settings_str(s)
function Settings:_init()
-- Default setting values.
self.permadeath = true
self.forward = 'w'
self.backward = 's'
self.stepleft = 'a'
self.stepright = 'd'
self.turnleft = 'q'
self.turnright = 'e'
self.musicvol = 75
self.soundvol = 100
end
function Settings:_parse_settings_str(s)
-- For each line in the data string.
for line in magiclines(s) do
-- Check if the line matches a key=value pair
local k, v = string.match(line, '(%w+)=(.+)')
if k and v then
-- If the line matches then check if it has a known settings key.
if self[k] ~= nil then
-- If the key is valid then cast the value to it's expected data type and store it.
-- Fail if the cast is not doable.
if v == 'true' then
if type(self[k]) ~= 'boolean' then error(string.format('Invalid value for %s in settings', k)) end
self[k] = true
@@ -53,18 +64,23 @@ function settings:_parse_settings_str(s)
self[k] = v
end
else
error(self[k])
-- Fail on an unknown key.
error(string.format('Unknown settings key "%s"', k))
end
end
end
end
function settings:_get_settings_str()
function Settings:_get_settings_str()
-- Start with the settings category.
local s = '[settings]\n'
-- For each value in the settings table.
for k, v in pairs(self) do
-- If the value isn't a function then it is a setting.
if type(v) ~= 'function' then
-- Format the value as key=value.
s = s .. string.format('%s=%s\n', k, v)
end
end
@@ -73,15 +89,18 @@ function settings:_get_settings_str()
end
function settings:_open_or_create()
function Settings:_open_or_create()
if not love.filesystem.exists(SETTINGS_PATH) then
-- If the settings file doesn't exist then create it with the default values.
local setts = self:_get_settings_str()
love.filesystem.write(SETTINGS_PATH, setts)
return setts
else
if not love.filesystem.isFile(SETTINGS_PATH) then
-- Fail if the settings path points to a directory.
error ("Settings path exists and isn't a file")
else
-- Else read the settings.
return love.filesystem.read(SETTINGS_PATH)
end
end
@@ -92,16 +111,19 @@ end
-- Public module methods
------------------------------------------------------------------------------
function settings:load_settings()
function Settings:load_settings()
-- Obtain the settings data and parse it.
local s = self:_open_or_create()
self:_parse_settings_str(s)
end
function settings:save_settings()
function Settings:save_settings()
if not love.filesystem.exists(SETTINGS_PATH) or love.filesystem.isFile(SETTINGS_PATH) then
-- If the settings file doesn't exist or it exists and is a file then save the current data.
love.filesystem.write(SETTINGS_PATH, self:_get_settings_str())
else
-- Fail if the settings path points to a directory.
error("Settings path exists and isn't a file")
end
end
@@ -111,4 +133,4 @@ end
-- Module return
------------------------------------------------------------------------------
return settings
return Settings()