remove unnecessary files

This commit is contained in:
duncathan 2021-11-30 22:39:57 -06:00
parent bd8e4889fa
commit 137e30edcc
155 changed files with 0 additions and 17362 deletions

View File

@ -1,23 +0,0 @@
# image: shru/arch-love-release:heavy
image: asmfreak/love-release:latest
variables:
ITCHIO_USER: shru
ITCHIO_GAME: cave-story-randomizer
CHANNEL: $CI_COMMIT_REF_NAME
stages:
- build-release
build-and-release:
stage: build-release
when: manual
script:
# I - Build
- cd src
- love-release -W
# II - Release to itch.io
- cd releases
- FILE=$(ls *-win32.zip) ;
zip -d "$FILE" "*/lovec.exe" "*/changes.txt" "*/readme.txt" "*.ico" ;
butler push "$FILE" "$ITCHIO_USER/$ITCHIO_GAME:win32-$CHANNEL"

View File

@ -1,48 +0,0 @@
language: python
os: linux
dist: xenial
addons:
apt:
update: true
packages:
- libzip-dev
- xvfb
services:
- xvfb
before_install:
- |
pip install hererocks
hererocks env --luarocks 3.0 --lua latest
export 'PATH='"$TRAVIS_BUILD_DIR"'/env/bin'":$PATH"
install:
- |
sudo add-apt-repository -y ppa:bartbes/love-stable
sudo apt-get -q update
sudo apt-get -y install love
luarocks install --server="http://luarocks.org/dev" lua-zip
luarocks install love-release
script:
- bash daily.sh
env:
global:
- secure: DU2Mq4FPbOidv0Go4b+2r5Vy7v0bbQo40ZZPkiwm/6nAJYkwySwVn2DyZGwh/kV4o6ZG0IgtLxPvHiF8lVZptbtkHJTt/GeokwwvsfpHJntPC9jyvXklFkkln9h2AgWrhTmFf+EYE4Mlm7FAjsr/9ZZHCuGn7wDCOBk3g4Zy7eAyKGqpYMN3296eUiZPXWtOAexmbavFI07pmu4ZyJ8K6HMmjd8YSAQpui3zJn/hMTupQm+o5s/ehZ72QnUyjRWdKvLlJCzORrajJxgYXQqLdjXVMcq4kuR4pDRnLdW9XBlAWiHOvj7wlVOUyBq2ZyhkTTNEYiQp+9UsIua1gG0hdLiOGH2ODtrb6NvQIRUfs5jE6xHmw3hQCfIe9itT+SbVQmuuTovM/MQTS/vH7k+AHtnJCGykMjAD6evNi1EkOG5oxrL7xEGPz7lzyVISoz0O4YmMWGF0qIzb68R11+Eo1URFrEUDxZc7F2kMoeAbnTxfiGABrMXrgIN7ReJhfD1z4ik6KwKBFw9AwJfldyb5w0QgqFAkkihqKGp5VbTMR6LeSN0Tq/Ic3QRvLHYt43+eoRXS4d2lvSo/0Nf7FftQPurbLI6c/r9nckwOZmjFicwYJ0Ulrbd46og74FUQZFSMcrVsiXo1PG2yTwu9bUvTBUHWdWh0o2xPpfAHbA05++c=
before_deploy:
- |
cd src; love-release -M; cd -
mv pre-edited-cs "src/releases/Cave Story"
cd src/releases
zip -9 -qur CaveStoryRandomizer-macos.zip "Cave Story"
zip -9 -qr CaveStoryRandomizer-linux.zip "Cave Story" CaveStoryRandomizer.love
cd -
# don't forget to create the windows releases!
deploy:
provider: releases
token:
secure: bMinTly2BOZCNQw625Fz744hvVQlnwZiC6pQZZx1f5evs0gi4TXbqqft5UN3orGgRiNnVnbf2qYC7PnS1LzynHUimdVk8iqfRKz4ADuWXyf9/i1gojzzSAI87lxbGaHxzqjZLtzf1zRonPlY1oj9X3a1eAle6EEAn/Utnm/jqZPhTYm8cMowLWqcVTcDOw1lhBpgsVYdej1wCaq7r2KBDvrqWZCdt8pJ2lcaShJEqmBONDvu9KO+gbNXwmhBAz4bG0DTVFhO459ZNjfNz749eiU7KhbxvFgz7O+j8HEdhlXznYgmzveUmVmhOSe3z037YwCNv+2ar0FITGkmTpgog0z2XuxDMb28Eakoc0uzaowwB68JRfEeRpBGidhSGraZ5DnHOS3QdDaEQ4jImv97DagiVoqcKm3JDGhGuvbI4dfPsXHKMncJC8cqf86NwSg63boabcoUTwzVLZdd4tbiOBqYPBgEtEz4uFgKvkrrzE0DoP1Efi1aZqcnOW6xckHbC261rxB5QG8WGcb1tDnmQgbhbEEguPHuzKBWP6ktpjpFdeS3Wbqb0UMaXQXMCcaMpJfCLNsLxQ6iHOh+XQhJREDPTuXm4F1m0BzSjBvQpWQ4tpO+kF1KiJ4wiwzn9jTfJxhgMgxEoY75dtH+E0K2jXLRezychR22V+7R9ifNLns=
file:
- src/releases/*.zip
file_glob: true
draft: true
skip_cleanup: true
on:
tags: true

Binary file not shown.

Before

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 432 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 227 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 418 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 195 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 577 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 328 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 346 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 471 B

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 180 KiB

View File

@ -1,45 +0,0 @@
if io then
io.stdout:setvbuf("no")
end
function love.conf(t)
t.window = {
title = "Cave Story Randomizer",
icon = 'assets/icon/randoconfig.png',
width = 640,
height = 480,
resizable = false,
}
t.version = '11.1'
t.console = false
t.identity = 'CaveStoryRandomizer'
t.accelerometerjoystick = false
t.gammacorrect = false
t.modules.audio = false
t.modules.joystick = false
t.modules.physics = false
t.modules.sound = false
t.modules.thread = false
t.modules.touch = false
t.modules.video = false
t.releases = {
-- This is the name of the zip archive which contains your game.
title = 'CaveStoryRandomizer',
-- This is the name of your game's executable.
package = 'Cave Story Randomizer',
loveVersion = '11.2',
version = 'v2.0',
author = 'duncathan',
email = 'dunc@duncathan.com',
description = 'A randomizer for Cave Story',
homepage = 'https://github.com/cave-story-randomizer/cave-story-randomizer',
-- MacOS needs this.
identifier = 'CaveStoryRandomizer',
excludeFileList = {
},
compile = false,
releaseDirectory = 'releases',
}
end

Binary file not shown.

View File

@ -1,550 +0,0 @@
local function lifeCapsule3()
return {
name = "Life Capsule",
script = "<EVE0012",
attributes = {"nonProgressive", "hp3", "helpful"},
hints = {"a Life Capsule", "a little HP"}
}
end
local function lifeCapsule4()
return {
name = "Life Capsule",
script = "<EVE0013",
attributes = {"nonProgressive", "hp4", "helpful"},
hints = {"a Life Capsule", "some decent HP"}
}
end
local function lifeCapsule5()
return {
name = "Life Capsule",
script = "<EVE0014",
attributes = {"nonProgressive", "hp5", "helpful"},
hints = {"a Life Capsule", "a lot of HP"}
}
end
local function missiles()
return {
name = "Missile Expansion",
script = "<EVE0030",
attributes = {"weapon", "missileLauncher", "nonProgressive", "helpful"},
}
end
local function event(n)
return {
name = "Event: " .. n,
attributes = {"event", "abstract"},
placed = true
}
end
local function objective(n, eve)
return {
name = n,
attributes = {"objective", "abstract"},
placed = true,
script = eve
}
end
local function _itemData()
local data = {
-------------
-- WEAPONS --
-------------
polarStar1 = {
name = "Polar Star",
script = "<EVE0002",
attributes = {"weapon", "weaponBoss", "weaponSN", "polarStar", "mandatory"},
hints = {"the gunsmith's pride"}
},
polarStar2 = {
name = "Polar Star",
script = "<EVE0002",
attributes = {"weapon", "weaponBoss", "weaponSN", "polarStar", "mandatory"},
hints = {"the gunsmith's pride"}
},
missileLauncher = {
name = "Missile Launcher",
script = "<EVE0030",
attributes = {"weapon", "weaponSN", "nonProgressive", "helpful"},
},
superMissileLauncher = {
name = "Super Missile Launcher",
script = "<EVE0033",
attributes = {"weapon", "weaponSN", "missileLauncher", "nonProgressive", "helpful"},
},
fireball = {
name = "Fireball",
script = "<EVE0004",
attributes = {"weapon", "weaponBoss", "mandatory"},
hints = {"the Fireball"}
},
snake = {
name = "Snake",
script = "<EVE0005",
attributes = {"weapon", "weaponBoss", "nonProgressive", "weaponStrong", "helpful"},
hints = {"the Snake"}
},
bubbler = {
name = "Bubbler",
script = "<EVE0007",
attributes = {"weapon", "weaponBoss", "nonProgressive", "helpful"},
hints = {"the Bubbler"}
},
machineGun = {
name = "Machine Gun",
script = "<EVE0008",
attributes = {"weapon", "weaponBoss", "weaponSN", "flight", "mandatory", "weaponStrong"},
hints = {"the Machine Gun"}
},
blade = {
name = "Blade",
script = "<EVE0009",
attributes = {"weapon", "weaponBoss", "weaponSN", "mandatory", "weaponStrong"},
hints = {"the Blade"}
},
nemesis = {
name = "Nemesis",
script = "<EVE0010",
attributes = {"weapon", "weaponBoss", "weaponSN", "nonProgressive", "weaponStrong", "helpful"},
hints = {"the Nemesis"}
},
---------------
-- INVENTORY --
---------------
mapSystem = {
name = "Map System",
script = "<EVE0052",
attributes = {"nonProgressive", "map"},
hints = {"a map", "an electronic device"}
},
locket = {
name = "Silver Locket",
script = "<EVE0054",
attributes = {"mandatory"},
hints = {"some fishy jewelry", "a Mimiga's item"}
},
arthurKey = {
name = "Arthur's Key",
script = "<EVE0051",
attributes = {"mandatory"},
hints = {"Arthur's Key", "a key", "a Mimiga's item"}
},
idCard = {
name = "ID Card",
script = "<EVE0057",
attributes = {"mandatory"},
hints = {"the ID Card", "an electronic device"}
},
santaKey = {
name = "Santa's Key",
script = "<EVE0053",
attributes = {"mandatory"},
hints = {"Santa's Key", "a key", "a Mimiga's item"}
},
lipstick = {
name = "Chaco's Lipstick",
script = "<EVE0087",
attributes = {"nonProgressive", "useless"},
hints = {"some lipstick", "a Mimiga's item", "a lewd item"}
},
juice = {
name = "Jellyfish Juice",
script = "<EVE0058",
attributes = {"mandatory"},
hints = {"some juice", "a bomb ingredient"}
},
charcoal = {
name = "Charcoal",
script = "<EVE0062",
attributes = {"mandatory"},
hints = {"some charcoal", "a bomb ingredient"}
},
rustyKey = {
name = "Rusty Key",
script = "<EVE0059",
attributes = {"mandatory"},
hints = {"the Rusty Key", "a key"}
},
gumKey = {
name = "Gum Key",
script = "<EVE0060",
attributes = {"mandatory"},
hints = {"the Gum Key", "a key"}
},
gumBase = {
name = "Gum Base",
script = "<EVE0061",
attributes = {"mandatory"},
hints = {"some gum", "a bomb ingredient"}
},
bomb = {
name = "Bomb",
script = "<EVE0063",
attributes = {"mandatory"},
hints = {"a bomb"}
},
panties = {
name = "Curly's Panties",
script = "<EVE0085",
attributes = {"nonProgressive", "useless"},
hints = {"a pair of panties", "a lewd item"}
},
puppy1 = {
name = "Hajime",
script = "<EVE0064",
attributes = {"puppy"}
},
puppy2 = {
name = "Kakeru",
script = "<EVE0064",
attributes = {"puppy"}
},
puppy3 = {
name = "Mick",
script = "<EVE0064",
attributes = {"puppy"}
},
puppy4 = {
name = "Nene",
script = "<EVE0064",
attributes = {"puppy"}
},
puppy5 = {
name = "Shinobu",
script = "<EVE0064",
attributes = {"puppy"}
},
lifepot = {
name = "Life Pot",
script = "<EVE0065",
attributes = {"nonProgressive", "helpful"},
hints = {"a Life Pot", "some medicine"}
},
turbocharge = {
name = "Turbocharge",
script = "<EVE0070",
attributes = {"nonProgressive", "helpful"},
hints = {"the Turbocharge"}
},
clinicKey = {
name = "Clinic Key",
script = "<EVE0067",
attributes = {"mandatory"},
hints = {"the Clinic Key", "a key"}
},
armsBarrier = {
name = "Arms Barrier",
script = "<EVE0069",
attributes = {"nonProgressive", "helpful"},
hints = {"the Arms Barrier"}
},
cureAll = {
name = "Cure-All",
script = "<EVE0066",
attributes = {"mandatory"},
hints = {"the Cure-All", "some medicine"}
},
booster1 = {
name = "Booster",
script = "<EVE0068",
attributes = {"flight", "booster", "mandatory"},
hints = {"a Booster", "a rocket component"}
},
booster2 = {
name = "Booster",
script = "<EVE0068",
attributes = {"flight", "booster", "mandatory"},
hints = {"a Booster", "a rocket component"}
},
towRope = {
name = "Tow Rope",
script = "<EVE0080",
attributes = {"mandatory"},
hints = {"the Tow Rope"}
},
airTank = {
name = "Curly's Air Tank",
script = "<EVE0071",
attributes = {"mandatory"},
hints = {"an air tank"}
},
alienMedal = {
name = "Alien Medal",
script = "<EVE0086",
attributes = {"nonProgressive", "useless"},
hints = {"the Alien Medal", "a badge of victory"}
},
whimsicalStar = {
name = "Whimsical Star",
script = "<EVE0088",
attributes = {"nonProgressive", "helpful"},
hints = {"the Whimsical Star"}
},
nikumaru = {
name = "Nikumaru Counter",
script = "<EVE0072",
attributes = {"nonProgressive"},
hints = {"the Nikumaru Counter"}
},
teleportKey = {
name = "Teleporter Room Key",
script = "<EVE0075",
attributes = {"mandatory"},
hints = {"the Teleporter Room Key", "a key"}
},
letter = {
name = "Sue's Letter",
script = "<EVE0076",
attributes = {"mandatory"},
hints = {"Sue's Letter", "a Mimiga's item"}
},
mask = {
name = "Mimiga Mask",
script = "<EVE0074",
attributes = {"mandatory"},
hints = {"the Mimiga Mask"}
},
brokenSprinkler = {
name = "Broken Sprinkler",
script = "<EVE0078",
attributes = {"mandatory"},
hints = {"the Broken Sprinkler", "a sprinkler"}
},
newSprinkler = {
name = "Sprinkler",
script = "<EVE0079",
attributes = {"mandatory"},
hints = {"the Sprinkler", "a sprinkler", "a rocket component"}
},
controller = {
name = "Controller",
script = "<EVE0077",
attributes = {"mandatory"},
hints = {"the Controller", "a rocket component", "an electronic device"}
},
mushroomBadge = {
name = "Mushroom Badge",
script = "<EVE0083",
attributes = {"mandatory"},
hints = {"the Mushroom Badge", "a badge of victory"}
},
maPignon = {
name = "Ma Pignon",
script = "<EVE0084",
attributes = {"mandatory"},
hints = {"Ma Pignon", "a living being"}
},
mrLittle = {
name = "Little Man",
script = "<EVE0082",
attributes = {"mandatory"},
hints = {"Mr Little", "a living being"}
--placed = true
},
ironBond = {
name = "Iron Bond",
script = "<EVE0089",
attributes = {"mandatory"},
hints = {"the Iron Bond"}
},
clayMedal = {
name = "Clay Figure Medal",
script = "<EVE0081",
attributes = {"nonProgressive", "useless"},
hints = {"the Clay Figure Medal", "a badge of victory"}
},
-------------------
-- LIFE CAPSULES --
-------------------
capsule3A = lifeCapsule3(), -- First Cave
capsule3B = lifeCapsule3(), -- Yamashita Farm
capsule3C = lifeCapsule3(), -- Egg Corridor (Basil)
capsule4A = lifeCapsule4(), -- Egg Corridor (Cthulhu)
capsule5A = lifeCapsule5(), -- Grasstown
capsule5B = lifeCapsule5(), -- Execution Chamber
capsule5C = lifeCapsule5(), -- Sand Zone (Upper)
capsule5D = lifeCapsule5(), -- Sand Zone (Lower)
capsule5E = lifeCapsule5(), -- Labyrinth
capsule5F = lifeCapsule5(), -- Plantation (West)
capsule4B = lifeCapsule4(), -- Plantation (Puppy)
capsule5G = lifeCapsule5(), -- Sacred Grounds
--------------
-- MISSILES --
--------------
missileA = missiles(), -- Grasstown
missileB = missiles(), -- Grasstown Hut
missileC = missiles(), -- Egg Corridor?
missileD = { -- Egg Observation Room? (arbitrarily making this a backup Super Missile chest)
name = "Missile Expansion (Super Missile alt)",
script = "<EVE0038",
attributes = {"weapon", "missileLauncher", "nonProgressive", "helpful"}
},
missileHell = {
name = "Missile Expansion",
script = "<EVE0035",
attributes = {"weapon", "missileLauncher", "nonProgressive", "helpful"}
},
------------
-- EVENTS --
------------
eventSue = event("Saved Sue"),
eventFans = event("Activated Fans"),
eventKazuma = event("Saved Kazuma"),
eventOmega = event("Defeated Omega"),
eventToroko = event("Defeated Toroko+"),
eventCore = event("Defeated the Core"),
eventCurly = event("Saved Curly"),
eventRocket = event("Built Rocket"),
----------------
-- GAME MODES --
----------------
objBadEnd = objective("Bad Ending", "<FL+6003"),
objNormalEnd = objective("Normal Ending", "<FL+6000"),
objBestEnd = objective("Best Ending", "<FL+6001"),
objAllBosses = objective("All Bosses", "<FL+6002<IT+0005"),
obj100Percent = objective("100%", "<FL+6004<IT+0005")
}
local function initializeHints(item)
local hintArray = {
--mandatory = {"a required item"},
puppy = {"a puppy", "a living being"},
--helpful = {"a helpful item"},
--useless = {"a useless item"},
weapon = {"a weapon"},
--weaponSN = {"a weapon that breaks blocks"},
--weaponStrong = {"a strong weapon"},
flight = {"a method of flight", "flight"},
missileLauncher = {"a Missile upgrade"}
}
item.hints = item.hints or {} -- initialize item's hints array if not already
-- loop through item's attributes and add any matching hints from the hintArray table
for k,v in ipairs(item.attributes) do
for k2,v2 in ipairs(hintArray[v] or {}) do
table.insert(item.hints, v2)
end
end
end
local array = {}
for k, t in pairs(data) do
t.key = k
t.placed = t.placed or false
t.attributes = t.attributes or {}
table.insert(t.attributes, k)
table.insert(array, t)
initializeHints(t)
end
return array
end
local C = Class:extend()
function C:new()
self.itemData = _itemData()
end
function C:getByKey(key)
return _.filter(self.itemData, function(k,v) return v.key == key end)[1]
end
function C:_getItems(filterFn)
return _.filter(self.itemData, filterFn)
end
function C:getItems()
return self:_getItems(function(k,v) return true end)
end
function C:getItemsByAttribute(attribute, onlyUnplaced)
onlyUnplaced = onlyUnplaced or false
return self:_getItems(function(k,v) return _.contains(v.attributes, attribute) and not (onlyUnplaced and v.placed) end)
end
function C:getEvents()
return self:getItemsByAttribute("event")
end
function C:getOptionalItems(onlyUnplaced)
return self:getItemsByAttribute("nonProgressive", onlyUnplaced)
end
function C:getMandatoryItems(onlyUnplaced)
return self:getItemsByAttribute("mandatory", onlyUnplaced)
end
function C:getMandatory()
return self:_getItems(function(k,v) return not _.contains(v.attributes, "nonProgressive") end)
end
function C:getUnplacedItems()
return self:_getItems(function(k,v) return not v.placed end)
end
function C:unplacedString()
local s = "\r\nUnplaced items:"
for k,v in pairs(self:getUnplacedItems()) do s = s .. "\r\n" .. v.name end
return s
end
local function _hint(message, l, ending)
ending = ending or "<END"
local MSGBOXLIMIT = 35
local PATTERN = " [^ ]*$"
local line1, line2, line3 = "", "", ""
local split = 1
line1 = message:sub(split, split+MSGBOXLIMIT)
if line1:find(PATTERN) and #message > MSGBOXLIMIT then
line1 = line1:sub(1, line1:find(PATTERN))
split = line1:find(PATTERN)+split
if split ~= MSGBOXLIMIT then line2 = "\r\n" end
line2 = line2 .. message:sub(split, split+MSGBOXLIMIT)
if line2:find(PATTERN) and #message > MSGBOXLIMIT*2 then
line2 = line2:sub(1, line2:find(PATTERN))
split = line2:find(PATTERN)+split-2
if split ~= MSGBOXLIMIT then line3 = "\r\n" end
line3 = line3 .. message:sub(split, split+MSGBOXLIMIT)
end
end
local s = "<PRI<MSG<TUR" .. line1 .. line2 .. line3 .. "<NOD" .. ending
return {
name = ("%q [%s] [%s]"):format(message, l.item.name, l.name),
atrributes = {"hint", "abstract"},
placed = true,
script = s
}
end
function C:createHint(l, ending)
local function pick(t) return t[love.math.random(#t)] end
local location, item = l:getHint()
local starts = {"I hear that ", "Rumour has it, ", "They say "}
local mids = {" can be found ", " is ", " is hidden "}
local message = (pick(starts) or "") .. (pick(item) or "") .. (pick(mids) or "") .. (pick(location) or "") .. "."
return _hint(message, l, ending)
end
function C:prebuiltHint(l, ending)
return _hint(l:getPrebuiltHint(), l, ending)
end
return C

View File

@ -1,53 +0,0 @@
local C = Class:extend()
function C:new(name, map, event, region, hints)
self.name = name
self.map = map
self.event = event
self.region = region
self.hintList = hints or {}
end
function C:fill(item, items)
local old = self.item
self:setItem(item)
if self:canAccess(items) then return true end
self:setItem(old)
return false
end
function C:canAccess(items)
if not self.region:canAccess(items) then return false end
if self.requirements == nil then return true end
return self.requirements(self, items)
end
function C:hasItem()
return self.item ~= nil
end
function C:setItem(item)
item.placed = true
item.location_name = self.name
self.item = item
end
function C:writeItem(tscFiles, item)
item = item or self.item
if item == nil then
logError("No item at " .. self.name)
return
end
if self.map == nil or self.event == nil or item.script == nil then return end
tscFiles[self.map]:placeItemAtLocation(item, self)
end
function C:getHint()
return _.append(self.region.hintList, self.hintList), self.item.hints
end
function C:getPrebuiltHint()
return nil
end
return C

View File

@ -1,280 +0,0 @@
local song = Class:extend()
function song:new(name, id, jingle, game)
self.name = name
self.id = id
self.jingle = jingle or false
self.game = game or "vanilla"
end
local songs = {
xxxx = song("XXXX", "0000", true),
mischievousRobot = song("Mischievous Robot", "0001"),
safety = song("Safety", "0002"),
gameOver = song("Game Over", "0003", true),
gravity = song("Gravity", "0004"),
onToGrasstown = song("On to Grasstown", "0005"),
meltdown = song("Meltdown 2", "0006"),
eyesOfFlame = song("Eyes of Flame", "0007"),
gestation = song("Gestation", "0008"),
mimigaTown = song("Mimiga Town", "0009"),
getItem = song("Get Item!", "0010", true),
balrogsTheme = song("Balrog's Theme", "0011"),
cemetary = song("Cemetary", "0012"),
plant = song("Plant", "0013"),
pulse = song("Pulse", "0014"),
victory = song("Victory!", "0015", true),
getLifeCapsule = song("Get Life Capsule!", "0016", true),
tyrant = song("Tyrant", "0017"),
run = song("Run!", "0018"),
jenka1 = song("Jenka 1", "0019"),
labyrinthFight = song("Labyrinth Fight", "0020"),
access = song("Access", "0021"),
oppression = song("Oppression", "0022"),
geothermal = song("Geothermal", "0023"),
caveStory = song("Cave Story", "0024"),
moonsong = song("Moonsong", "0025"),
herosEnd = song("Hero's End", "0026"),
scorchingBack = song("Scorching Back", "0027"),
quiet = song("Quiet", "0028"),
finalCave = song("Final Cave", "0029"),
balcony = song("Balcony", "0030"),
charge = song("Charge", "0031"),
lastBattle = song("Last Battle", "0032"),
theWayBackHome = song("The Way Back Home", "0033"),
zombie = song("Zombie", "0034"),
breakDown = song("Break Down", "0035"),
runningHell = song("Running Hell", "0036"),
jenka2 = song("Jenka 2", "0037"),
livingWaterway = song("Living Waterway", "0038"),
sealChamber = song("Seal Chamber", "0039"),
torokosTheme = song("Toroko's Theme", "0040"),
white = song('"White"', "0041"),
windFortress = song("Wind Fortress", "0042", false, "beta"),
halloween2 = song("Halloween 2", "0043", false, true),
peopleOfTheRoot = song("People of the Root", "0044", false, "beta"),
pierWalk = song("Pier Walk", "0045", false, "beta"),
snoopyCake = song("Snoopy Cake", "0046", false, "beta"),
dataSlots = song("Data Slots", "0047", false, "kero"),
catAndFrog = song("Cat & Frog Corp.", "0048", false, "kero"),
--itsMyBlaster = song("It's My Blaster!", "0049", false, "kero"),
shoppingCart = song("Shopping Cart", "0050", false, "kero"),
prothallium = song("Prothallium", "0051", false, "kero"),
hardCording = song("Hard Cording", "0052", false, "kero"),
newItem = song("New Item!", "0053", false, "kero"), --kind of jingle, kind of not?
checkinOut = song("Check'IN Out", "0054", false, "kero"),
sukima = song("SUKIMA", "0055", false, "kero"),
relaxation = song("Relaxation", "0056", false, "kero"),
chemistry = song("Chemistry", "0057", false, "kero"),
--arrival = song("Arrival", "0058", false, "kero"),
freezeDraft = song("Freeze Draft", "0059", false, "kero"),
magicNumber = song("Magic Number", "0060", false, "kero"),
--timeTable = song("Time Table", "0061", false, "kero"),
--number1119 = song("Number 1119", "0061", false, "kero"),
trainStation = song("Train Station", "0062", false, "kero"),
--totoStation = song("ToTo Station", "0063", false, "kero"),
kaishaMan = song("Kaisha Man", "0064", false, "kero"),
zombeat = song("Zombeat", "0065", false, "kero"),
--oyasumiSong = song("Oyasumi Song", "0066", false, "kero"),
--changeSpec = song("Change Spec", "0067", false, "kero"),
--curtainRise = song("Curtain Rise", "0068", false, "kero"),
--creditsOfKero = song("Credits of Kero", "0069", false, "kero"),
--myPreciousDays = song("My Precious Days", "0070", false, "kero"),
--excuseMe = song("Excuse Me...", "0071", false, "kero"),
}
local cue = Class:extend()
function cue:new(map, events, default)
self.map = map
self.events = events
self.songid = default
self.default = default
end
local cues = {
cue("0", {"0100"}, "0033"),
cue("0", {"0200"}, "0001"),
cue("Pens1", {"0090", "0091", "0092", "0093", "0094", "0099"}, "0002"),
cue("Pens1", {"0095", "0098"}, "0014"),
cue("Eggs", {"0090", "0091", "0092", "0093", "0094", "0095", "0099", "0503"}, "0001"),
cue("Eggs", {"0600"}, "0004"),
cue("EggX", {"0095"}, "0014"),
cue("EggR", {"0090", "0091", "0092", "0093", "0094"}, "0008"),
cue("Weed", {"0090", "0091", "0092", "0093", "0094", "0098", "0099", "0600"}, "0005"),
cue("Santa", {"0090", "0091", "0092", "0093", "0094"}, "0002"),
cue("Santa", {"0099"}, "0028"),
cue("Chako", {"0090", "0091", "0092", "0093", "0094"}, "0002"),
cue("Chako", {"0099"}, "0028"),
cue("MazeI", {"0090", "0091", "0092", "0093", "0094", "0400", "0601"}, "0019"),
cue("Sand", {"0090", "0091", "0092", "0093", "0094", "0099", "0210", "0601"}, "0006"),
cue("Sand", {"0202"}, "0007"),
cue("Mimi", {"0090", "0091", "0092", "0093", "0094", "0302"}, "0009"),
cue("Mimi", {"0095", "0096", "0097", "0098", "0099"}, "0028"),
cue("Cave", {"0090", "0091", "0092", "0093", "0094"}, "0008"),
cue("Start", {"0090", "0091", "0092", "0093", "0094"}, "0008"),
cue("Barr", {"0090", "0091", "0092", "0093", "0094", "0402", "1001"}, "0008"),
cue("Barr", {"1000"}, "0011"),
cue("Barr", {"1000"}, "0004"),
cue("Pool", {"0090", "0091", "0092", "0093", "0094"}, "0009"),
cue("Pool", {"0095", "0096", "0097", "0098", "0099", "0410"}, "0028"),
cue("Cemet", {"0090", "0091", "0092", "0093", "0094"}, "0012"),
cue("Plant", {"0090", "0091", "0092", "0093", "0094"}, "0013"),
cue("Plant", {"0095", "0096", "0097", "0098", "0099"}, "0028"),
cue("Shelt", {"0090", "0091", "0092", "0093", "0094", "0099"}, "0008"),
cue("Comu", {"0090", "0091", "0092", "0093", "0094"}, "0009"),
cue("Comu", {"0095", "0096", "0097", "0098", "0099"}, "0028"),
cue("Cthu", {"0090", "0091", "0092", "0093", "0094"}, "0008"),
cue("Malco", {"0090", "0091", "0092", "0093", "0094", "0203"}, "0008"),
cue("Malco", {"0200", "0204"}, "0004"),
cue("Malco", {"0200"}, "0011"),
cue("Frog", {"0090", "0091", "0092", "0093", "0094", "1000"}, "0008"),
cue("Frog", {"0202"}, "0011"),
cue("Frog", {"0202"}, "0007"),
cue("Curly", {"0090", "0091", "0092", "0093", "0094"}, "0002"),
cue("Curly", {"0300"}, "0004"),
cue("WeedB", {"0302"}, "0004"),
cue("Stream", {"0090", "0091", "0092", "0093", "0094"}, "0018"),
cue("Jenka1", {"0090", "0091", "0092", "0093", "0094"}, "0019"),
cue("Gard", {"0502"}, "0017"),
cue("Gard", {"0502"}, "0018"),
cue("Gard", {"0502"}, "0004"),
cue("Jenka2", {"0090", "0091", "0092", "0093", "0095", "0200"}, "0019"),
cue("Jenka2", {"0094"}, "0011"),
cue("SandE", {"0090", "0091", "0092", "0093", "0094", "0600"}, "0006"),
cue("SandE", {"0600"}, "0011"),
cue("MazeH", {"0090", "0091", "0092", "0093", "0094"}, "0019"),
cue("MazeW", {"0090", "0091", "0092", "0093", "0094", "1000"}, "0037"),
cue("MazeW", {"0302"}, "0007"),
cue("MazeO", {"0090", "0091", "0092", "0093", "0094"}, "0002"),
cue("MazeD", {"0400"}, "0004"),
cue("MazeA", {"0090", "0091", "0092", "0093", "0094", "0301"}, "0008"),
cue("MazeB", {"0090", "0091", "0092", "0093", "0094", "0099"}, "0008"),
cue("MazeS", {"0090", "0091", "0092", "0093", "0094", "0310", "0600"}, "0008"),
cue("MazeS", {"0321"}, "0011"),
cue("MazeS", {"0321"}, "0004"),
cue("MazeM", {"0090", "0091", "0092", "0093", "0094", "0301"}, "0020"),
cue("Drain", {"0090", "0091", "0092", "0093", "0094", "0150"}, "0008"),
cue("Drain", {"0095", "0096", "0097"}, "0023"),
cue("Almond", {"0090", "0091", "0092", "0093", "0094", "0361"}, "0023"),
cue("Almond", {"0452", "0500"}, "0022"),
cue("River", {"0090", "0091", "0092", "0093", "0094", "0095"}, "0038"),
cue("Eggs2", {"0090", "0091", "0092", "0093", "0094", "0099"}, "0027"),
cue("Cthu2", {"0090", "0091", "0092", "0093", "0094"}, "0008"),
cue("EggR2", {"0090", "0091", "0092", "0093", "0094", "1000"}, "0008"),
cue("EggR2", {"0304"}, "0004"),
cue("EggX2", {"0090", "0091", "0092", "0093", "0095"}, "0014"),
cue("Oside", {"0090", "0091", "0092", "0093", "0094"}, "0025"),
cue("Oside", {"0402"}, "0026"),
cue("Itoh", {"0090", "0091", "0092", "0093", "0094", "0095"}, "0008"),
cue("Cent", {"0090", "0091", "0092", "0093", "0094"}, "0024"),
cue("Jail1", {"0090", "0091", "0092", "0093", "0094", "0220"}, "0008"),
cue("Momo", {"0090", "0091", "0092", "0093", "0094", "0280", "0281"}, "0002"),
cue("Lounge", {"0090", "0091", "0092", "0093", "0094"}, "0002"),
cue("Jail2", {"0090", "0091", "0092", "0093", "0094", "0099"}, "0008"),
cue("Blcny1", {"0090", "0091", "0092", "0093", "0094"}, "0030"),
cue("Priso1", {"0090", "0091", "0092", "0093", "0094"}, "0029"),
cue("Ring1", {"0090", "0091", "0092", "0093", "0094", "0097", "0098", "0402"}, "0030"),
cue("Ring1", {"0502", "0503"}, "0007"),
cue("Ring1", {"0099"}, "0018"),
cue("Ring2", {"0090", "0091", "0092", "0093", "0094", "0098", "0420"}, "0030"),
cue("Ring2", {"0502"}, "0007"),
cue("Ring2", {"0410"}, "0031"),
cue("Ring2", {"0099"}, "0018"),
cue("Prefa1", {"0090", "0091", "0092", "0093", "0094"}, "0030"),
cue("Priso2", {"0090", "0091", "0092", "0093", "0094", "0250"}, "0029"),
cue("Priso2", {"0241"}, "0004"),
cue("Ring3", {"0502"}, "0034"),
cue("Ring3", {"0502"}, "0032"),
cue("Ring3", {"0600"}, "0018"),
cue("Little", {"0090", "0091", "0092", "0093", "0094"}, "0002"),
cue("Blcny2", {"0090", "0091", "0092", "0093", "0094", "0310"}, "0018"),
cue("Blcny2", {"0400"}, "0035"),
cue("Pixel", {"0090", "0091", "0092", "0093", "0094", "0253"}, "0014"),
cue("Hell1", {"0090", "0091", "0092", "0094", "0096"}, "0036"),
cue("Hell2", {"0090", "0091", "0092", "0093", "0094"}, "0036"),
cue("Hell3", {"0090", "0091", "0092", "0093", "0094"}, "0036"),
cue("Hell3", {"0300"}, "0007"),
cue("Mapi", {"0420"}, "0004"),
cue("Statue", {"0100"}, "0024"),
cue("Ballo1", {"0095"}, "0039"),
cue("Ballo1", {"0500"}, "0004"),
cue("Ballo1", {"0900"}, "0007"),
cue("Ballo1", {"1000"}, "0032"),
cue("Pole", {"0090", "0091", "0092", "0093", "0094", "0095"}, "0008"),
cue("Ballo2", {"0500"}, "0034")
}
local music = Class:extend()
function music:new()
self.vanillaEnabled = true
self.betaEnabled = false
self.keroEnabled = false
self.songs = songs
self.cues = cues
self.flavor = "Shuffle"
end
local _isValid = function(key, song, self, canRemap)
if song.jingle then return false end
if song.game == "vanilla" and (canRemap or self.vanillaEnabled) then return true end
if song.game == "beta" and self.betaEnabled then return true end
if song.game == "kero" and self.keroEnabled then return true end
return false
end
function music:getShuffledSongs()
return _.shuffle(_.filter(self.songs, _isValid, self))
end
function music:getCues()
return self.cues
end
function music:shuffleMusic(tscFiles)
if self.flavor == "Shuffle" then self:_shuffle(tscFiles) end
if self.flavor == "Random" then self:_random(tscFiles) end
if self.flavor == "Chaos" then self:_chaos(tscFiles) end
end
-- SHUFFLE songs: every cue with a given song becomes the same, new song
function music:_shuffle(tscFiles)
local shuffled = self:getShuffledSongs()
local idmap = _.map(self.songs, function (k,v)
-- don't remap any invalid songs
if not _isValid(k,v,self,true) then return v.id, v.id end
if #shuffled == 0 then shuffled = self:getShuffledSongs() end
return v.id, _.pop(shuffled).id
end)
for k,cue in pairs(self.cues) do
cue.songid = idmap[cue.songid]
end
self:writeCues(tscFiles)
end
-- RANDOMIZE songs: any cue can play any song
function music:_random(tscFiles)
for k,cue in pairs(self.cues) do
cue.songid = self:getShuffledSongs()[1].id
end
self:writeCues(tscFiles)
end
-- CHAOTICALLY RANDOMIZE songs: nearly any <CMU can play any song
function music:_chaos(tscFiles)
for k,cue in pairs(self.cues) do
for k,event in ipairs(cue.events) do
tscFiles[cue.map]:placeSongAtCue(self:getShuffledSongs()[1].id, event, cue.map, cue.default)
end
end
end
function music:writeCues(tscFiles)
for k,cue in pairs(self.cues) do
for k,event in ipairs(cue.events) do
tscFiles[cue.map]:placeSongAtCue(cue.songid, event, cue.map, cue.default)
end
end
end
return music

View File

@ -1,38 +0,0 @@
local C = Class:extend()
function C:new(worldGraph, name, hints)
self.locations = {}
self.world = worldGraph
self.name = name
self.order = worldGraph.order
worldGraph.order = worldGraph.order + 1
self.hintList = hints or {}
end
function C:canAccess(items)
if self.requirements == nil then return true end
return self.requirements(self, items)
end
function C:getLocation(key)
return self.locations[key]
end
function C:getLocations(filterFn)
filterFn = filterFn or function(k,v) return true end
return _.filter(self.locations, filterFn)
end
function C:getEmptyLocations()
return self:getLocations(function(k,v) return not v:hasItem() end)
end
function C:getFilledLocations()
return self:getLocations(function(k,v) return v:hasItem() end)
end
function C:writeItems(tscFiles)
for key, location in pairs(self.locations) do location:writeItem(tscFiles) end
end
return C

View File

@ -1,772 +0,0 @@
local Region = require 'database.region'
local Location = require 'database.location'
function _has(items, attribute)
return _count(items, attribute, 1)
end
function _num(items, attribute)
return #_.filter(items, function(k,v) return _.contains(v.attributes or {}, attribute) end)
end
function _count(items, attribute, num)
return _num(items, attribute) >= num
end
function _hp(items, hp)
return 3 + (_num(items, "hp3") * 3) + (_num(items, "hp4") * 4) + (_num(items, "hp5") * 5)
end
local firstCave = Region:extend()
function firstCave:new(worldGraph)
firstCave.super.new(self, worldGraph, "First Cave", {"in First Cave"})
self.locations = {
firstCapsule = Location("First Cave Life Capsule", "Cave", "0401", self),
gunsmithChest = Location("Hermit Gunsmith Chest", "Pole", "0202", self, {"at someone's house", "with Tetsuzou"}),
gunsmith = Location("Tetsuzou", "Pole", "0303", self, {"at someone's house", "with Tetsuzou"}),
objective = Location("Game Settings", "Start", "0201", self)
}
self.requirements = function(self, items)
return _has(items, "flight") and _has(items, "weaponSN") and self.world.regions.mimigaVillage:canAccess(items)
end
self.locations.gunsmith.requirements = function(self, items)
return _has(items, "polarStar") and _has(items, "eventCore")
end
-- individual location access requirement overrides
self.locations.firstCapsule.canAccess = function(self, items)
if self.region.world:StartPoint() then return true end -- TODO remember to add a check for Entrance Rando here
return Location.canAccess(self, items)
end
self.locations.gunsmithChest.canAccess = function(self, items)
if self.region.world:StartPoint() then return true end -- TODO remember to add a check for Entrance Rando here
return Location.canAccess(self, items)
end
end
local mimigaVillage = Region:extend()
function mimigaVillage:new(worldGraph)
mimigaVillage.super.new(self, worldGraph, "Mimiga Village", {"in Mimiga Village"})
self.locations = {
yamashita = Location("Yamashita Farm", "Plant", "0401", self, {"underwater", "in a garden"}),
reservoir = Location("Reservoir", "Pool", "0301", self, {"underwater"}),
mapChest = Location("Mimiga Village Chest", "Mimi", "0202", self),
assembly = Location("Assembly Hall Fireplace", "Comu", "0303", self, {"in a fireplace"}),
mrLittle = Location("Mr. Little (Graveyard)", "Cemet", "0202", self, {"in the Graveyard", "with a very little man, hiding in the grass"}),
grave = Location("Arthur's Grave", "Cemet", "0301", self, {"with a fallen hero..", "in the Graveyard"}),
mushroomChest = Location("Storage? Chest", "Mapi", "0202", self, {"in the Graveyard"}),
maPignon = Location("Ma Pignon Boss", "Mapi", "0501", self, {"behind a boss", "behind Ma Pignon", "in the Graveyard"})
}
self.requirements = function(self, items)
if self.world:StartPoint() then
return _has(items, "weaponSN")
elseif self.world:Camp() or self.world:Arthur() then
return _has(items, "arthurKey") and self.world.regions.arthur:canAccess(items) or self.world.regions.waterway:canAccess(items)
end
end
self.locations.assembly.requirements = function(self, items) return _has(items, "juice") end
self.locations.mrLittle.requirements = function(self, items) return _has(items, "locket") end
self.locations.grave.requirements = function(self, items) return _has(items, "locket") end
self.locations.mushroomChest.requirements = function(self, items)
return _has(items, "flight") and _has(items, "locket") and _has(items, "eventCurly")
end
self.locations.maPignon.requirements = function(self, items)
-- stupid mushroom is invincible to the blade and machinegun for some reason
if _has(items, "flight") and _has(items, "locket") and _has(items, "mushroomBadge") then
if _has(items, "polarStar") or _has(items, "fireball") or _has(items, "bubbler")
or _has(items, "machineGun") or _has(items, "snake") or _has(items, "nemesis") then
return true
end
end
return false
end
--self.locations.mrLittle:setItem(self.world.items:getByKey("mrLittle"))
end
local arthur = Region:extend()
function arthur:new(worldGraph)
arthur.super.new(self, worldGraph, "Arthur's House", {"in Mimiga Village", "at Arthur's House", "at someone's house"})
self.locations = {
risenBooster = Location("Professor Booster", "Pens1", "0652", self)
}
self.requirements = function(self, items)
if self.world:StartPoint() then
return _has(items, "arthurKey") and _has(items, "weaponSN") and self.world.regions.mimigaVillage:canAccess(items)
elseif self.world:Camp() then
return self.world.regions.labyrinthB:canAccess(items)
elseif self.world:Arthur() then
return true
end
end
self.locations.risenBooster.requirements = function(self, items) return _has(items, "eventCore") end
end
local eggCorridor1 = Region:extend()
function eggCorridor1:new(worldGraph)
eggCorridor1.super.new(self, worldGraph, "Egg Corridor", {"in the Egg Corridor", "in the normal Egg Corridor"})
self.locations = {
basil = Location("Basil Spot", "Eggs", "0403", self),
cthulhu = Location("Cthulhu's Abode", "Eggs", "0404", self),
eggItem = Location("Egg Chest", "Egg6", "0201", self, {"in an egg"}),
observationChest = Location("Egg Observation Room Chest", "EggR", "0301", self, {"in the Egg Observation Room"}),
eventSue = Location("Saved Sue", nil, nil, self)
}
self.requirements = function(self, items) return self.world.regions.arthur:canAccess(items) end
self.locations.cthulhu.requirements = function(self, items) return _has(items, "weaponSN") or _has(items, "flight") or self.region.world:_dboost(items, 'cthulhu') end
self.locations.eventSue.requirements = function(self, items) return _has(items, "idCard") and _has(items, "weaponBoss") end
self.locations.eventSue:setItem(self.world.items:getByKey("eventSue"))
end
local grasstownWest = Region:extend()
function grasstownWest:new(worldGraph)
grasstownWest.super.new(self, worldGraph, "Grasstown (West)", {"in Grasstown", "in West Grasstown"})
self.locations = {
keySpot = Location("West Grasstown Floor", "Weed", "0700", self),
jellyCapsule = Location("West Grasstown Ceiling", "Weed", "0701", self),
santa = Location("Santa", "Santa", "0501", self, {"at Santa's House", "at someone's house"}),
charcoal = Location("Santa's Fireplace", "Santa", "0302", self, {"at Santa's House", "at someone's house", "in a fireplace"}),
chaco = Location("Chaco's Bed, where you two Had A Nap", "Chako", "0211", self, {"at Chaco's House", "in Chaco's bed", "at someone's house"}),
kulala = Location("Kulala Chest", "Weed", "0702", self, {"in a sticky situation"})
}
self.requirements = function(self, items)
return self.world.regions.arthur:canAccess(items)
end
self.locations.santa.requirements = function(self, items) return _has(items, "santaKey") end
self.locations.charcoal.requirements = function(self, items) return _has(items, "santaKey") and _has(items, "juice") end
self.locations.chaco.requirements = function(self, items) return _has(items, "santaKey") end
self.locations.kulala.requirements = function(self, items) return _has(items, "santaKey") and _has(items, "weapon") end
end
local grasstownEast = Region:extend()
function grasstownEast:new(worldGraph)
grasstownEast.super.new(self, worldGraph, "Grasstown (East)", {"in Grasstown", "in East Grasstown"})
self.locations = {
kazuma1 = Location("Kazuma Crack", "Weed", "0800", self, {"with Kazuma", "at the Shelter"}),
kazuma2 = Location("Kazuma Chest", "Weed", "0801", self, {"at the Shelter"}),
execution = Location("Execution Chamber", "WeedD", "0305", self),
outsideHut = Location("Grasstown East Chest", "Weed", "0303", self, {"at Grasstown Hut"}),
hutChest = Location("Grasstown Hut", "WeedB", "0301", self, {"at Grasstown Hut"}),
gumChest = Location("Gum Chest", "Frog", "0300", self, {"behind a boss", "behind Balfrog"}),
malco = Location("MALCO", "Malco", "0350", self, {"with MALCO", "behind a boss", "behind Balrog 2"}),
eventFans = Location("Activated Grasstown Fans", nil, nil, self),
eventKazuma = Location("Saved Kazuma", nil, nil, self)
}
self.requirements = function(self, items)
if not self.world.regions.arthur:canAccess(items) then return false end
if _has(items, "flight") or _has(items, "juice") or (self.world:_dboost(items, 'chaco') and _has(items, "weapon")) or self.world:_dboost(items, 'paxChaco') then
if self.world.regions.grasstownWest:canAccess(items) then return true end
end
if _has(items, "eventKazuma") and _has(items, "weaponSN") and self.world.regions.plantation:canAccess(items) then return true end
return false
end
self.locations.kazuma2.requirements = function(self, items) return _has(items, "rustyKey") end
self.locations.execution.requirements = function(self, items) return _has(items, "weaponSN") end
self.locations.hutChest.requirements = function(self, items) return _has(items, "eventFans") or _has(items, "flight") or self.region.world:_dboost(items, 'flightlessHut') end
self.locations.gumChest.requirements = function(self, items)
if _has(items, "gumKey") and _has(items, "weaponBoss") then
if _has(items, "eventFans") or _has(items, "flight") then return true end
end
return false
end
self.locations.malco.requirements = function(self, items) return _has(items, "eventFans") and _has(items, "juice") and _has(items, "charcoal") and _has(items, "gumBase") end
self.locations.malco.getPrebuiltHint = function(self) return ("BUT ALL I KNOW HOW TO DO IS MAKE %s..."):format(self.item.hints[love.math.random(#self.item.hints)]:upper()) end
self.locations.eventFans.requirements = function(self, items) return _has(items, "rustyKey") and _has(items, "weaponBoss") end
self.locations.eventFans:setItem(self.world.items:getByKey("eventFans"))
self.locations.eventKazuma.requirements = function(self, items) return _has(items, "bomb") end
self.locations.eventKazuma:setItem(self.world.items:getByKey("eventKazuma"))
end
local upperSandZone = Region:extend()
function upperSandZone:new(worldGraph)
upperSandZone.super.new(self, worldGraph, "Sand Zone (Upper)", {"in the Sand Zone", "in Upper Sand Zone"})
self.locations = {
curly = Location("Curly Boss", "Curly", "0518", self, {"in the Sand Zone Residence", "with Curly", "at someone's house"}),
panties = Location("Curly's Closet", "CurlyS", "0421", self, {"in the Sand Zone Residence", "at someone's house"}),
curlyPup = Location("Puppy (Curly)", "CurlyS", "0401", self, {"in the Sand Zone Residence", "at someone's house", "where a puppy once was"}),
sandCapsule = Location("Polish Spot", "Sand", "0502", self),
eventOmega = Location("Defeated Omega", nil, nil, self)
}
self.requirements = function(self, items)
return self.world.regions.arthur:canAccess(items) and _has(items, "weaponSN")
end
self.locations.curly.requirements = function(self, items) return _has(items, "polarStar") end
self.locations.panties.requirements = function(self, items) return _has(items, "weaponBoss") end
self.locations.curlyPup.requirements = function(self, items) return _has(items, "weaponBoss") end
self.locations.eventOmega.requirements = function(self, items) return _has(items, "weaponBoss") end
self.locations.eventOmega:setItem(self.world.items:getByKey("eventOmega"))
end
local lowerSandZone = Region:extend()
function lowerSandZone:new(worldGraph)
lowerSandZone.super.new(self, worldGraph, "Sand Zone (Lower)", {"in the Sand Zone", "in Lower Sand Zone"})
self.locations = {
chestPup = Location("Puppy (Chest)", "Sand", "0423", self, {"where a puppy once was"}),
darkPup = Location("Puppy (Dark)", "Dark", "0401", self, {"where a puppy once was"}),
runPup = Location("Puppy (Run)", "Sand", "0422", self, {"where a puppy once was", "with a puppy"}),
sleepyPup = Location("Puppy (Sleep)", "Sand", "0421", self, {"where a puppy once was", "at the far end of Sand Zone"}),
pawCapsule = Location("Pawprint Spot", "Sand", "0503", self),
jenka = Location("Jenka", "Jenka2", "0221", self, {"with Jenka", "at someone's house", "where a puppy once was"}),
king = Location("King", "Gard", "0602", self, {"with a fallen hero..", "in a garden", "behind a boss", "behind Toroko+"}),
eventToroko = Location("Defeated Toroko+", nil, nil, self)
}
self.requirements = function(self, items)
if self.world:StartPoint() or self.world:Arthur() then
return _has(items, "eventOmega") and self.world.regions.upperSandZone:canAccess(items)
elseif self.world:Camp() then
return self.world.regions.labyrinthW:canAccess(items)
end
end
self.locations.jenka.requirements = function(self, items) return _count(items, "puppy", 5) end
self.locations.king.requirements = function(self, items) return _has(items, "eventToroko") end
self.locations.eventToroko.requirements = function(self, items)
return _count(items, "puppy", 5) and _has(items, "weaponBoss")
end
self.locations.eventToroko:setItem(self.world.items:getByKey("eventToroko"))
self.locations.jenka.getPrebuiltHint = function(self) return ("perhaps I'll give you %s in return..."):format(self.item.hints[love.math.random(#self.item.hints)]) end
end
local labyrinthW = Region:extend()
function labyrinthW:new(worldGraph)
labyrinthW.super.new(self, worldGraph, "Labyrinth (West)", {"in the Labyrinth", "in West Labyrinth"})
self.locations = {
mazeCapsule = Location("Labyrinth Life Capsule", "MazeI", "0301", self),
turboChaba = Location("Chaba Chest (Machine Gun)", "MazeA", "0502", self, {"with Chaba", "in Chaba's Machine Gun chest"}),
snakeChaba = Location("Chaba Chest (Fireball)", "MazeA", "0512", self, {"with Chaba", "in Chaba's Fireball chest"}),
whimChaba = Location("Chaba Chest (Spur)", "MazeA", "0522", self, {"with Chaba", "in Chaba's Spur chest"}),
campChest = Location("Camp Chest", "MazeO", "0401", self, {"in the Camp"}),
physician = Location("Dr. Gero", "MazeO", "0305", self, {"in the Camp"}),
puuBlack = Location("Puu Black Boss", "MazeD", "0201", self)
}
self.requirements = function(self, items)
if self.world:StartPoint() or self.world:Arthur() then
if not self.world.regions.arthur:canAccess(items) then return false end
if _has(items, "eventToroko") and self.world.regions.lowerSandZone:canAccess(items) then return true end
if _has(items, "flight") and _has(items, "weaponBoss") and self.world.regions.labyrinthB:canAccess(items) then return true end
return false
elseif self.world:Camp() then
return true
end
end
self.locations.mazeCapsule.requirements = function(self, items) return _has(items, "weapon") end
self.locations.turboChaba.requirements = function(self, items) return _has(items, "machineGun") end
self.locations.snakeChaba.requirements = function(self, items) return _has(items, "fireball") end
self.locations.whimChaba.requirements = function(self, items) return _count(items, "polarStar", 2) end
self.locations.campChest.requirements = function(self, items) return _has(items, "flight") or self.region.world:Camp() or self.region.world:_dboost(items, 'camp') end
self.locations.puuBlack.requirements = function(self, items) return _has(items, "clinicKey") and _has(items, "weaponBoss") end
end
local labyrinthB = Region:extend()
function labyrinthB:new(worldGraph)
labyrinthB.super.new(self, worldGraph, "Labyrinth B", {"in the Labyrinth"})
self.locations = {
fallenBooster = Location("Booster Chest", "MazeB", "0502", self)
}
self.requirements = function(self, items)
if self.world:StartPoint() or self.world:Arthur() then
return self.world.regions.arthur:canAccess(items)
elseif self.world:Camp() then
return self.world.regions.labyrinthW:canAccess(items) and _has(items, "weaponBoss")
end
end
end
local boulder = Region:extend()
function boulder:new(worldGraph)
boulder.super.new(self, worldGraph, "Labyrinth (East)", {"in the Labyrinth", "in East Labyrinth", "behind a boss"})
self.locations = { --include core locations since core access reqs are identical to boulder chamber
boulderChest = Location("Boulder Chest", "MazeS", "0202", self, {"behind Balrog 3"}),
coreSpot = Location("Robot's Arm", "Almond", "0243", self, {"underwater", "behind the Core"}),
curlyCorpse = Location("Drowned Curly", "Almond", "1111", self, {"with Curly", "underwater", "behind the Core"}),
eventCore = Location("Defeated Core", nil, nil, self)
}
self.requirements = function(self, items)
if not self.world.regions.arthur:canAccess(items) then return false end
if _has(items, "cureAll") and _has(items, "weaponBoss") then
if self.world.regions.labyrinthW:canAccess(items) then return true end
end
return false
end
self.locations.eventCore:setItem(self.world.items:getByKey("eventCore"))
end
local labyrinthM = Region:extend()
function labyrinthM:new(worldGraph)
labyrinthM.super.new(self, worldGraph, "labyrinthM")
self.requirements = function(self, items)
if not self.world.regions.arthur:canAccess(items) then return false end
if self.world.regions.boulder:canAccess(items) then return true end
if _has(items, "flight") and self.world.regions.labyrinthW:canAccess(items) then return true end
return false
end
end
local waterway = Region:extend()
function waterway:new(worldGraph)
waterway.super.new(self, worldGraph, "Waterway", {"in the Waterway", "underwater", "behind a boss", "behind Ironhead"})
self.locations = {
ironhead = Location("Ironhead Boss", "Pool", "0412", self),
eventCurly = Location("Saved Curly", nil, nil, self)
}
self.requirements = function(self, items)
if _has(items, "airTank") and _has(items, "weaponBoss") and self.world.regions.labyrinthM:canAccess(items) then return true end
return false
end
self.locations.eventCurly.requirements = function(self, items) return _has(items, "eventCore") and _has(items, "towRope") end
self.locations.eventCurly:setItem(self.world.items:getByKey("eventCurly"))
end
local eggCorridor2 = Region:extend()
function eggCorridor2:new(worldGraph)
eggCorridor2.super.new(self, worldGraph, "Egg Corridor?", {"in the Egg Corridor", "in ruined Egg Corridor"})
self.locations = {
dragonChest = Location("Dragon Chest", "Eggs2", "0321", self),
sisters = Location("Sisters Boss", "EggR2", "0303", self, {"behind a boss", "in the Egg Observation Room", "behind the Sisters"})
}
self.requirements = function(self, items)
if not self.world.regions.arthur:canAccess(items) then return false end
if _has(items, "eventCore") then return true end
if _has(items, "eventKazuma") and self.world.regions.outerWall:canAccess(items) then return true end
return false
end
self.locations.dragonChest.requirements = function(self, items) return _has(items, "weaponSN") or _has(items, "eventCore") end
self.locations.sisters.requirements = function(self, items) return _has(items, "weaponBoss") or (self.region.world:_dboost(items, 'sisters') and _has(items, "flight")) end
end
local outerWall = Region:extend()
function outerWall:new(worldGraph)
outerWall.super.new(self, worldGraph, "Outer Wall", {"outside", "along the Outer Wall"})
self.locations = {
clock = Location("Clock Room", "Clock", "0300", self),
littleHouse = Location("Little House", "Little", "0204", self, {"with Mr. Little", "at someone's house"})
}
self.requirements = function(self, items)
if not self.world.regions.arthur:canAccess(items) and (_has(items, "weapon") or _hp(items) >= 9 or _has(items, "flight")) then return false end
if _has(items, "eventKazuma") and _has(items, "flight") and _has(items, "eventCore") then return true end
if _has(items, "teleportKey") and self.world.regions.plantation:canAccess(items) then return true end
return false
end
self.locations.littleHouse.requirements = function(self, items) return _has(items, "flight") and _has(items, "blade") and _has(items, "mrLittle") end
self.locations.littleHouse.getPrebuiltHint = function(self) return ("He was exploring the island with %s..."):format(self.item.hints[love.math.random(#self.item.hints)]) end
end
local plantation = Region:extend()
function plantation:new(worldGraph)
plantation.super.new(self, worldGraph, "Plantation", {"in the Plantation", "in a garden"})
self.locations = {
kanpachi = Location("Kanpachi's Bucket", "Cent", "0268", self, {"with Kanpachi", "underwater"}),
jail1 = Location("Jail no. 1", "Jail1", "0301", self, {"with Mahin"}),
momorin = Location("Chivalry Sakamoto's Wife", "Momo", "0201", self, {"with Momorin", "at someone's house"}),
sprinkler = Location("Broken Sprinkler", "Cent", "0417", self, {"for Mimiga only"}),
megane = Location("Megane", "Lounge", "0204", self, {"for Mimiga only"}),
itoh = Location("Itoh", "Itoh", "0405", self, {"with Itoh"}),
plantCeiling = Location("Plantation Platforming Spot", "Cent", "0501", self),
plantPup = Location("Plantation Puppy", "Cent", "0452", self, {"with a puppy"}),
curlyShroom = Location("Jammed it into Curly's Mouth", "Cent", "0324", self, {"with Curly"}),
eventRocket = Location("Built Rocket", nil, nil, self)
}
self.requirements = function(self, items)
if not self.world.regions.arthur:canAccess(items) then return false end
if _has(items, "teleportKey") then return true end
if self.world.regions.outerWall:canAccess(items) then return true end
if _has(items, "eventKazuma") and _has(items, "weaponSN") then return true end
return false
end
self.locations.jail1.requirements = function(self, items) return _has(items, "teleportKey") end
self.locations.momorin.requirements = function(self, items) return _has(items, "letter") and _has(items, "booster") end
self.locations.sprinkler.requirements = function(self, items) return _has(items, "mask") end
self.locations.megane.requirements = function(self, items) return _has(items, "brokenSprinkler") and _has(items, "mask") end
self.locations.itoh.requirements = function(self, items) return _has(items, "letter") end
self.locations.plantCeiling.requirements = function(self, items) return _has(items, "flight") or self.region.world:_dboost(items, 'plantation') end
self.locations.plantPup.requirements = function(self, items) return _has(items, "eventRocket") end
self.locations.curlyShroom.requirements = function(self, items) return _has(items, "eventCurly") and _has(items, "maPignon") end
self.locations.eventRocket.requirements = function(self, items)
return _has(items, "letter") and _has(items, "booster") and _has(items, "controller") and _has(items, "newSprinkler")
end
self.locations.eventRocket:setItem(self.world.items:getByKey("eventRocket"))
end
local lastCave = Region:extend()
function lastCave:new(worldGraph)
lastCave.super.new(self, worldGraph, "Last Cave", {"behind a boss", "in Last Cave", "behind the Red Demon"})
self.locations = {
redDemon = Location("Red Demon Boss", "Priso2", "0300", self)
}
self.requirements = function(self, items) return _has(items, "weaponBoss") and _count(items, "booster", 2) and (_has(items, "eventRocket") or (self.world:_dboost(items, 'rocket') and _has(items, "machineGun") and self.world.regions.plantation:canAccess(items))) end
end
local endgame = Region:extend()
function endgame:new(worldGraph)
endgame.super.new(self, worldGraph, "Sacred Grounds", {"in Hell"})
self.locations = {
hellB1 = Location("Hell B1 Spot", "Hell1", "0401", self),
hellB3 = Location("Hell B3 Chest", "Hell3", "0400", self)
}
local function prebuilt(self)
local s = self.item.hints[love.math.random(#self.item.hints)]
return ("%s."):format(s:sub(1,1):upper() .. s:sub(2)) -- make first character uppercase
end
self.locations.hellB1.getPrebuiltHint = prebuilt
self.locations.hellB3.getPrebuiltHint = prebuilt
self.requirements = function(self, items) return false end -- just pretend you never get here
end
local hintRegion = Region:extend()
function hintRegion:new(worldGraph)
hintRegion.super.new(self, worldGraph, "Hints")
self.locations = {
cthulhuEgg = Location("Cthulhu (his Abode)", "Cthu", "0200", self),
cthulhuWeed1 = Location("Cthulhu (West Grasstown)", "Weed", "0201", self),
cthulhuWeed2 = Location("Cthulhu (East Grasstown)", "Weed", "0205", self),
cthulhuPlant = Location("Cthulhu (Plantation)", "Cent", "0310", self),
bluebotEgg = Location("Blue Robot (Egg Corridor)", "Eggs", "0200", self),
bluebotEgg2 = Location("Blue Robot (Egg Corridor?)", "Eggs2", "0210", self),
bluebotMaze = Location("Blue Robot (Labyrinth I #1)", "MazeI", "0500", self),
bluebotMaze2 = Location("Blue Robot (Labyrinth I #2)", "MazeI", "0502", self),
mrsLittle = Location("Mrs. Little", "Little", "0212", self),
malco = Location("MALCO", "Malco", "0306", self),
jenka = Location("Jenka", "Jenka1", "0201", self),
numahachi1 = Location("Numahachi 1", "Statue", "0301", self),
numahachi2 = Location("Numahachi 2", "Statue", "0302", self)
}
-- they'll appear as filled so they get left out of the regular hints
self.locations.mrsLittle.item = {}
self.locations.malco.item = {}
self.locations.jenka.item = {}
self.locations.numahachi1.item = {}
self.locations.numahachi2.item = {}
end
local worldGraph = Class:extend()
function worldGraph:new(items)
self.items = items
self.order = 0
self.spawn = ""
self.seqbreak = false
self.dboosts = {
cthulhu = {hp = 3, enabled = true},
chaco = {hp = 5, enabled = true},
paxChaco = {hp = 10, enabled = true},
flightlessHut = {hp = 3, enabled = true},
-- revChaco = {hp = 3, enabled = true},
-- paxRevChaco = {hp = 8, enabled = true},
camp = {hp = 9, enabled = true},
sisters = {hp = 0, enabled = true},
plantation = {hp = 15, enabled = true},
rocket = {hp = 3, enabled = true},
}
self.regions = {
firstCave = firstCave(self),
mimigaVillage = mimigaVillage(self),
arthur = arthur(self),
eggCorridor1 = eggCorridor1(self),
grasstownWest = grasstownWest(self),
grasstownEast = grasstownEast(self),
upperSandZone = upperSandZone(self),
lowerSandZone = lowerSandZone(self),
labyrinthW = labyrinthW(self),
labyrinthB = labyrinthB(self),
boulder = boulder(self),
labyrinthM = labyrinthM(self),
waterway = waterway(self),
eggCorridor2 = eggCorridor2(self),
outerWall = outerWall(self),
plantation = plantation(self),
lastCave = lastCave(self),
endgame = endgame(self)
}
self.hintregion = hintRegion(self)
self.noFallingBlocks = false
end
function worldGraph:_dboost(items, key)
return self.seqbreak and self.dboosts[key].enabled and _hp(items) > self.dboosts[key].hp
end
function worldGraph:StartPoint() return self.spawn == "Start Point" end
function worldGraph:Arthur() return self.spawn == "Arthur's House" end
function worldGraph:Camp() return self.spawn == "Camp" end
function worldGraph:getSpawnScript()
local initialHPCounter = "<FL+4011<FL+4012" -- initializes the HP counter to 3 HP
local mapFlagsCMP = "<MP+0040<MP+0043" -- Camp and Labyrinth B
local mapFlagsSoftlock = "<MP+0032<MP+0033<MP+0036" -- Small Room (Curly), Jenka's House (both)
local baseStartScript = initialHPCounter .. mapFlagsCMP .. mapFlagsSoftlock -- TODO: remove mapFlagsSoftlock when playing ER
if self:StartPoint() then return baseStartScript .. "<FL+6200<EVE0091" end
local earlyGameFlags = "<FL+0301<FL+0302<FL+1641<FL+1642<FL+0320<FL+0321" -- flags set during first cave in normal gameplay
baseStartScript = baseStartScript .. earlyGameFlags
if self:Arthur() then return baseStartScript .. "<FL+6201<TRA0001:0094:0008:0004" end
if self:Camp() then return baseStartScript .. "<FL+6202<TRA0040:0094:0014:0009" end
end
function worldGraph:canBeatGame(items, obj)
if obj == "objBadEnd" then
end
local function normalReqs(self, items) return _has(items, "eventRocket") and _has(items, "eventSue") and self.regions.plantation:canAccess(items) end
if obj == "objNormalEnd" then return normalReqs(self, items) end
local function bestReqs(self, items) return normalReqs(self, items) and _has(items, "ironBond") and _count(items, "booster", 2) end
if obj == "objBestEnd" then return bestReqs(self, items) end
local function bossReqs(self, items)
if not (bestReqs(self, items) and _has(items, "weaponBoss")) then return false end
-- Igor, Balrog 1, Balrog 3, Core, Sisters
if not (_has(items, "eventSue") and _has(items, "eventToroko") and _has(items, "eventCore")) then return false end
-- Balrog 2, Balfrog
if not (_has(items, "rustyKey") and _has(items, "gumKey") and self.regions.grasstownEast:canAccess(items)) then return false end
-- Curly, Omega
if not (self.regions.upperSandZone:canAccess(items)) then return false end
-- Toroko+
if not self.regions.lowerSandZone.locations.king:canAccess(items) then return false end
-- Puu Black, Monster X
if not (self.regions.labyrinthW:canAccess(items) and _has(items, "clinicKey")) then return false end
-- Ironhead
if not self.regions.waterway:canAccess(items) then return false end
-- Ma Pignon
if not self.regions.mimigaVillage.locations.maPignon:canAccess(items) then return false end
-- Remaining bosses are all covered by the normal requirements
return true
end
if obj == "objAllBosses" then return bossReqs(self, items) end
return false -- I don't actually think the check below works as intended, false is sufficient for now since 100% and completable logic should be mutually exclusive anyway
--return #items > #self:getLocations() - #self:getHintLocations() - 2 -- removing ANY items (besides in hell) from 100% makes a seed uncompletable
end
function worldGraph:getLocations()
local locations = {}
for key, region in pairs(self.regions) do
for k, location in pairs(region:getLocations()) do
table.insert(locations, location)
end
end
return locations
end
function worldGraph:getObjectiveSpot()
return {self.regions.firstCave.locations.objective}
end
function worldGraph:getMALCO()
return {self.regions.grasstownEast.locations.malco}
end
function worldGraph:getCamp()
return {self.regions.labyrinthW.locations.physician, self.regions.labyrinthW.locations.campChest}
end
function worldGraph:getPuppySpots()
return {
self.regions.upperSandZone.locations.curly,
self.regions.upperSandZone.locations.curlyPup,
self.regions.upperSandZone.locations.panties,
self.regions.upperSandZone.locations.sandCapsule,
self.regions.lowerSandZone.locations.chestPup,
self.regions.lowerSandZone.locations.darkPup,
self.regions.lowerSandZone.locations.runPup,
self.regions.lowerSandZone.locations.sleepyPup,
self.regions.lowerSandZone.locations.pawCapsule
}
end
function worldGraph:getFirstCaveSpots()
return {
self.regions.firstCave.locations.firstCapsule,
self.regions.firstCave.locations.gunsmithChest
}
end
function worldGraph:getHellSpots()
return self.locationsArray(self.regions.endgame:getLocations())
end
function worldGraph:getEmptyLocations()
local locations = {}
for key, region in pairs(self.regions) do
for k, location in pairs(region:getEmptyLocations()) do
table.insert(locations, location)
end
end
return locations
end
function worldGraph:emptyString()
local s = "\r\nEmpty locations:"
for key, region in pairs(self.regions) do
for k, l in pairs(region:getEmptyLocations()) do
s = s .. "\r\n" .. l.name
end
end
return s
end
function worldGraph:getFilledLocations(realOnly)
local locations = {}
for key, region in pairs(self.regions) do
for k, location in pairs(region:getFilledLocations()) do
if not realOnly or not _.find(location.item.attributes,"abstract") then table.insert(locations, location) end
end
end
return locations
end
function worldGraph:getHintLocations()
return self.hintregion:getEmptyLocations()
end
function worldGraph:getHintableLocations(obj)
local locations = {}
for k, location in pairs(_.shuffle(self:getFilledLocations(true))) do
if location:getPrebuiltHint() ~= nil then
-- do nothing
elseif (obj == "objBadEnd" and location.item.name == "Rusty Key") or (obj ~= "objBadEnd" and location.item.name == "ID Card") then
table.insert(locations, 1, location) -- put that item on the top to guarantee a hint for it
elseif (self:Camp() or self:Arthur()) and location.item.name == "Arthur's Key" then
table.insert(locations, 1, location)
else
table.insert(locations, location)
end
end
return _.slice(locations, 1, #self:getHintLocations())
end
function worldGraph:writeItems(tscFiles)
for key, region in pairs(self.regions) do region:writeItems(tscFiles) end
self.hintregion:writeItems(tscFiles)
end
function worldGraph:collect(preCollectedItems, locations, singleSphere)
local collected = _.clone(preCollectedItems, singleSphere) or {}
local availableLocations = locations or self:getFilledLocations()
local inventory = (singleSphere and preCollectedItems) or collected
local foundItems = 0
repeat
foundItems = 0
-- Collect accessible items and remove those locations from the table
-- (to prevent double-collecting, which is bad for Polar Star/Booster)
local j, n = 1, #availableLocations
for i = 1, n do
local location = availableLocations[i]
if location:canAccess(inventory) then
table.insert(collected, location.item)
foundItems = foundItems + 1
availableLocations[i] = nil
else
if j ~= i then
availableLocations[j] = availableLocations[i]
availableLocations[i] = nil
end
j = j + 1
end
end
until foundItems == 0
--[[local s = "Collected items: "
for k,v in ipairs(collected) do s = s .. v.name .. ", " end
logDebug(s)]]
return collected, availableLocations
end
function worldGraph.locationsArray(locations)
local array = {}
for k, v in pairs(locations) do
table.insert(array, v)
end
return array
end
function worldGraph:serialize()
local array = {}
for k,v in pairs(self.regions) do
table.insert(array, v)
end
table.insert(array, self.hintregion)
local function sort(a,b)
return a.order < b.order
end
local lines = {}
for k,r in ipairs(_.sort(array,sort)) do
if next(r.locations) then
table.insert(lines, "")
table.insert(lines, "Region: " .. r.name)
for k2,l in pairs(r.locations) do
if l.item ~= nil and not _has({l.item}, "event") then
table.insert(lines, "\t " .. l.name .. ": " .. l.item.name)
end
end
end
end
return lines
end
function worldGraph:logLocations()
for k,v in ipairs(self:serialize()) do
logSpoiler(v)
end
end
return worldGraph

View File

@ -1,69 +0,0 @@
local C = Class:extend()
local ITEM_DATA = require 'database.items'
function C:new()
self._left = {}
for k, v in pairs(ITEM_DATA) do
local item = _.clone(v)
table.insert(self._left, item)
end
self._placed = {}
end
local function _filterAny(item) return true end
function C:getAny()
return self:_getItem(_filterAny)
end
function C:getAnyByAttribute(attribute)
function _filterAnyByAttribute(item, attribute)
for k, v in pairs(item.attributes) do
if v == attribute then return true
end
return false
end
return self:_getItem(_filterAnyByAttribute)
end
function C:placeAny()
return self:place(self:getAny())
end
function C:placeAnyByAttribute(attributes)
return self:place(self:getAnyByAttribute(attributes))
end
function C:_getItem(filterFn)
-- Filter down to only applicable items.
local applicable = {}
local indexMap = {}
for index, item in ipairs(self._left) do
if filterFn(item) then
table.insert(applicable, item)
indexMap[item] = index
end
end
assert(#applicable >= 1, 'No applicable items!')
-- Select an item.
local selected = _.sample(applicable)
local index = indexMap[selected]
return selected, index
end
function C:place(item, index)
table.remove(self._left, index)
table.insert(self._placed, item)
return item
end
function C:getPlacedItems(item)
local placed = {table.unpack(self._placed)} -- clone the table
if(item) then table.insert(placed, item) end
return placed
end
return C

View File

@ -1,260 +0,0 @@
--[[---------------
LuaBit v0.4
-------------------
a bitwise operation lib for lua.
http://luaforge.net/projects/bit/
How to use:
-------------------
bit.bnot(n) -- bitwise not (~n)
bit.band(m, n) -- bitwise and (m & n)
bit.bor(m, n) -- bitwise or (m | n)
bit.bxor(m, n) -- bitwise xor (m ^ n)
bit.brshift(n, bits) -- right shift (n >> bits)
bit.blshift(n, bits) -- left shift (n << bits)
bit.blogic_rshift(n, bits) -- logic right shift(zero fill >>>)
Please note that bit.brshift and bit.blshift only support number within
32 bits.
2 utility functions are provided too:
bit.tobits(n) -- convert n into a bit table(which is a 1/0 sequence)
-- high bits first
bit.tonumb(bit_tbl) -- convert a bit table into a number
-------------------
Under the MIT license.
copyright(c) 2006~2007 hanzhao (abrash_han@hotmail.com)
--]]---------------
do
------------------------
-- bit lib implementions
local function check_int(n)
-- checking not float
if(n - math.floor(n) > 0) then
error("trying to use bitwise operation on non-integer!")
end
end
local function to_bits(n)
check_int(n)
if(n < 0) then
-- negative
return to_bits(bit.bnot(math.abs(n)) + 1)
end
-- to bits table
local tbl = {}
local cnt = 1
while (n > 0) do
local last = math.fmod(n,2)
if(last == 1) then
tbl[cnt] = 1
else
tbl[cnt] = 0
end
n = (n-last)/2
cnt = cnt + 1
end
return tbl
end
local function tbl_to_number(tbl)
local n = table.getn(tbl)
local rslt = 0
local power = 1
for i = 1, n do
rslt = rslt + tbl[i]*power
power = power*2
end
return rslt
end
local function expand(tbl_m, tbl_n)
local big = {}
local small = {}
if(table.getn(tbl_m) > table.getn(tbl_n)) then
big = tbl_m
small = tbl_n
else
big = tbl_n
small = tbl_m
end
-- expand small
for i = table.getn(small) + 1, table.getn(big) do
small[i] = 0
end
end
local function bit_or(m, n)
local tbl_m = to_bits(m)
local tbl_n = to_bits(n)
expand(tbl_m, tbl_n)
local tbl = {}
local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n))
for i = 1, rslt do
if(tbl_m[i]== 0 and tbl_n[i] == 0) then
tbl[i] = 0
else
tbl[i] = 1
end
end
return tbl_to_number(tbl)
end
local function bit_and(m, n)
local tbl_m = to_bits(m)
local tbl_n = to_bits(n)
expand(tbl_m, tbl_n)
local tbl = {}
local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n))
for i = 1, rslt do
if(tbl_m[i]== 0 or tbl_n[i] == 0) then
tbl[i] = 0
else
tbl[i] = 1
end
end
return tbl_to_number(tbl)
end
local function bit_not(n)
local tbl = to_bits(n)
local size = math.max(table.getn(tbl), 32)
for i = 1, size do
if(tbl[i] == 1) then
tbl[i] = 0
else
tbl[i] = 1
end
end
return tbl_to_number(tbl)
end
local function bit_xor(m, n)
local tbl_m = to_bits(m)
local tbl_n = to_bits(n)
expand(tbl_m, tbl_n)
local tbl = {}
local rslt = math.max(table.getn(tbl_m), table.getn(tbl_n))
for i = 1, rslt do
if(tbl_m[i] ~= tbl_n[i]) then
tbl[i] = 1
else
tbl[i] = 0
end
end
--table.foreach(tbl, print)
return tbl_to_number(tbl)
end
local function bit_rshift(n, bits)
check_int(n)
local high_bit = 0
if(n < 0) then
-- negative
n = bit_not(math.abs(n)) + 1
high_bit = 2147483648 -- 0x80000000
end
for i=1, bits do
n = n/2
n = bit_or(math.floor(n), high_bit)
end
return math.floor(n)
end
-- logic rightshift assures zero filling shift
local function bit_logic_rshift(n, bits)
check_int(n)
if(n < 0) then
-- negative
n = bit_not(math.abs(n)) + 1
end
for i=1, bits do
n = n/2
end
return math.floor(n)
end
local function bit_lshift(n, bits)
check_int(n)
if(n < 0) then
-- negative
n = bit_not(math.abs(n)) + 1
end
for i=1, bits do
n = n*2
end
return bit_and(n, 4294967295) -- 0xFFFFFFFF
end
local function bit_xor2(m, n)
local rhs = bit_or(bit_not(m), bit_not(n))
local lhs = bit_or(m, n)
local rslt = bit_and(lhs, rhs)
return rslt
end
--------------------
-- bit lib interface
bit = {
-- bit operations
bnot = bit_not,
band = bit_and,
bor = bit_or,
bxor = bit_xor,
brshift = bit_rshift,
blshift = bit_lshift,
bxor2 = bit_xor2,
blogic_rshift = bit_logic_rshift,
-- utility func
tobits = to_bits,
tonumb = tbl_to_number,
}
end
--[[
for i = 1, 100 do
for j = 1, 100 do
if(bit.bxor(i, j) ~= bit.bxor2(i, j)) then
error("bit.xor failed.")
end
end
end
--]]

View File

@ -1,71 +0,0 @@
-- Copyright (c) 2014, rxi
--
-- Permission is hereby granted, free of charge, to any person obtaining a copy of
-- this software and associated documentation files (the "Software"), to deal in
-- the Software without restriction, including without limitation the rights to
-- use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-- of the Software, and to permit persons to whom the Software is furnished to do
-- so, subject to the following conditions:
--
-- The above copyright notice and this permission notice shall be included in all
-- copies or substantial portions of the Software.
--
-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-- SOFTWARE.
local Object = {}
Object.__index = Object
function Object:new()
end
function Object:extend()
local cls = {}
for k, v in pairs(self) do
if k:find("__") == 1 then
cls[k] = v
end
end
cls.__index = cls
cls.super = self
setmetatable(cls, self)
return cls
end
function Object:implement(...)
for _, cls in pairs({...}) do
for k, v in pairs(cls) do
if self[k] == nil and type(v) == "function" then
self[k] = v
end
end
end
end
function Object:is(T)
local mt = getmetatable(self)
while mt do
if mt == T then
return true
end
mt = getmetatable(mt)
end
return false
end
function Object:__tostring()
return "Object"
end
function Object:__call(...)
local obj = setmetatable({}, self)
obj:new(...)
return obj
end
return Object

View File

@ -1,602 +0,0 @@
--[[--
Widget attributes.
This module defines "attributes" (special fields) that are
recognized by all widgets. Their interpretation may vary
depending on the `type` of widget. Some widget types may also
recognize additional attributes.
Setting attributes can have side effects. For example, setting
`height` or `width` causes the parent widget and its descendants
to recalculate their size and position.
--]]--
local ROOT = (...):gsub('[^.]*$', '')
local Shortcut = require(ROOT .. 'shortcut')
local Attribute = {}
local function cascade (widget, attribute)
local value = rawget(widget, 'attributes')[attribute]
if value ~= nil then return value end
local parent = rawget(widget, 'parent')
return parent and parent[attribute]
end
--[[--
Type of widget.
Should contain a string identifying the widget's type.
After the layout is built, may be replaced by an array
of strings identifying multiple types. This is used
internally by some widgets to provide information about
the widget's state to the theme (themes describe the
appearance of widgets according to their type).
If a type is registered with the widget's layout, the registered
type initializer function will run once when the widget is constructed.
@see Widget.register
@attrib type
--]]--
Attribute.type = {}
function Attribute.type.set (widget, value)
local oldType = widget.attributes.type
widget.attributes.type = value
if value and not widget.hasType then
widget.hasType = true
local Widget = require(ROOT .. 'widget')
local decorate = Widget.typeDecorators[value]
if decorate then
decorate(widget)
end
end
end
--[[--
Widget identifier.
Should contain a unique string identifying the widget, if present.
A reference to the widget will be stored in the associated layout
in a property having the same name as the widget's id.
Setting this attribute re-registers the widget with its layout.
@attrib id
--]]--
Attribute.id = {}
function Attribute.id.set (widget, value)
local layout = widget.layout.master or widget.layout
local oldValue = widget.attributes.id
if oldValue then
layout[oldValue] = nil
end
if value then
layout[value] = widget
end
widget.attributes.id = value
end
--[[--
Widget value.
Some widget types expect the value to be of a specific type and
within a specific range. For example, `slider` and `progress`
widgets expect a normalized number, `text` widgets expect
a string, and `check` and `radio` widgets expect a boolean.
Setting this attribute bubbles the `Change` event.
@attrib value
--]]--
Attribute.value = {}
function Attribute.value.set (widget, value)
local oldValue = widget.value
widget.attributes.value = value
widget:bubbleEvent('Change', { value = value, oldValue = oldValue })
end
--[[--
Solidity.
Should true or false.
@attrib icon
--]]--
Attribute.solid = {}
function Attribute.solid.set (widget, value)
widget.attributes.solid = value
end
Attribute.solid.get = cascade
--[[--
Context menu.
- This attribute cascades.
@attrib context
--]]--
Attribute.context = {}
function Attribute.context.set (widget, value)
widget.attributes.context = value
if not value then return end
value.isContextMenu = true
widget.layout:createWidget { type = 'menu', value }
end
Attribute.context.get = cascade
--[[--
Widget style.
Should contain a string or array of strings identifying
style rules to be applied to the widget. When resolving
any attribute with a `nil` value, these style rules are
searched for a corresponding attribute.
Setting this attribute resets the `Font` and `Text` object
associated with this widget.
Setting this attribute recalculates the size and position
of the parent widget and its descendants.
@attrib style
--]]--
Attribute.style = {}
function Attribute.style.set (widget, value)
widget.attributes.style = value
widget.fontData = nil
widget.textData = nil
widget.reshape(widget.parent or widget)
end
--[[--
Status message.
Should contain a string with a short message describing the
purpose or state of the widget.
This message will appear in the last created `status` widget
in the same layout, or in the master layout if one exists.
- This attribute cascades.
@attrib status
--]]--
Attribute.status = {}
Attribute.status.get = cascade
--[[--
Scroll ability.
Should contain `true` or `false` (or `nil`).
If set to `true`, moving the scroll wheel over the widget will adjust
its scroll position when the widget's contents overflow its boundary.
@attrib scroll
--]]--
Attribute.scroll = {}
--[[--
Keyboard Attributes.
@section keyboard
--]]--
--[[--
Focusable.
Should contain `true` if the widget can be focused by pressing the tab key.
@attrib focusable
--]]--
Attribute.focusable = {}
--[[--
Keyboard shortcut.
Should contain a string representing a key and optional modifiers,
separated by dashes; for example `'ctrl-c'` or `'alt-shift-escape'`.
Pressing this key combination bubbles a `Press` event on the widget,
as if it had been pressed with a mouse or touch interface.
Setting this attribute re-registers the widget with its layout.
@attrib shortcut
--]]--
Attribute.shortcut = {}
local function setShortcut (layout, shortcut, value)
local mainKey, modifierFlags = Shortcut.parseKeyCombo(shortcut)
if mainKey then
layout.shortcuts[modifierFlags][mainKey] = value
end
end
function Attribute.shortcut.set (widget, value)
local layout = widget.layout.master or widget.layout
local oldValue = widget.attributes.shortcut
if oldValue then
if type(oldValue) == 'table' then
for _, v in ipairs(oldValue) do
setShortcut(layout, v, nil)
end
else
setShortcut(layout, oldValue, nil)
end
end
if value then
if type(value) == 'table' then
for _, v in ipairs(value) do
setShortcut(layout, v, widget)
end
else
setShortcut(layout, value, widget)
end
end
widget.attributes.shortcut = value
end
--[[--
Size Attributes.
Setting these attributes recalculates the size and position
of the parent widget and its descendants.
@section size
--]]--
--[[--
Flow axis.
Should equal either `'x'` or `'y'`. Defaults to `'y'`.
This attribute determines the placement and default dimensions
of any child widgets.
When flow is `'x'`, the `height` of child widgets defaults
to this widget's height, and each child is placed to the
right of the previous child. When flow is `'y'`, the `width`
of child widgets defaults to this widget's width, and each
child is placed below the previous child.
Setting this attribute resets the `Text` object associated
with this widget.
@attrib flow
--]]--
Attribute.flow = {}
function Attribute.flow.set (widget, value)
widget.attributes.flow = value
widget.textData = nil
widget.reshape(widget.parent or widget)
end
--[[--
Width.
This attribute may not always hold a numeric value.
To get the calculated width, use `Widget:getWidth`.
Setting this attribute when the `wrap` attribute is
also present resets the `Text` object associated
with this widget.
@attrib width
--]]--
Attribute.width = {}
function Attribute.width.set (widget, value)
if value ~= 'auto' then
value = value and math.max(value, widget.minwidth or 0)
end
widget.attributes.width = value
if widget.wrap then
widget.textData = nil
end
widget.reshape(widget.parent or widget)
end
--[[--
Height.
This attribute may not always hold a numeric value.
To get the calculated height, use `Widget:getHeight`.
@attrib height
--]]--
Attribute.height = {}
function Attribute.height.set (widget, value)
if value ~= 'auto' then
value = value and math.max(value, widget.minheight or 0)
end
widget.attributes.height = value
widget.reshape(widget.parent or widget)
end
--[[--
Minimum width.
@attrib minwidth
--]]--
Attribute.minwidth = {}
function Attribute.minwidth.set (widget, value)
local attributes = widget.attributes
attributes.minwidth = value
if type(value) == 'number' then
local current = attributes.width
if type(current) == 'number' then
attributes.width = math.max(current, value)
end
end
widget.reshape(widget.parent or widget)
end
--[[--
Minimum height.
@attrib minheight
--]]--
Attribute.minheight = {}
function Attribute.minheight.set (widget, value)
local attributes = widget.attributes
attributes.minheight = value
if type(value) == 'number' then
local current = attributes.height
if type(current) == 'number' then
attributes.height = math.max(current, value)
end
end
widget.reshape(widget.parent or widget)
end
--[[--
Font Attributes.
Setting these attributes resets the Font and Text
objects associated with the widget.
@section font
--]]--
--[[--
Font path.
Should contain a path to a TrueType font to use for displaying
this widget's `text`.
- This attribute cascades.
@attrib font
--]]--
Attribute.font = {}
local function resetFont (widget)
rawset(widget, 'fontData', nil)
rawset(widget, 'textData', nil)
for _, child in ipairs(widget) do
resetFont(child)
end
local items = widget.items
if items then
for _, child in ipairs(items) do
resetFont(child)
end
end
end
function Attribute.font.set (widget, value)
widget.attributes.font = value
resetFont(widget)
end
Attribute.font.get = cascade
--[[--
Font size.
Should contain a number representing the size of the font, in points.
Defaults to 12.
- This attribute cascades.
@attrib size
--]]--
Attribute.size = {}
function Attribute.size.set (widget, value)
widget.attributes.size = value
widget.fontData = nil
widget.textData = nil
end
Attribute.size.get = cascade
--[[--
Text Attributes.
Setting these attributes resets the Text object
associated with the widget.
@section text
--]]--
--[[--
Text to display.
@attrib text
--]]--
Attribute.text = {}
function Attribute.text.set (widget, value)
widget.attributes.text = value
widget.textData = nil
end
--[[--
Text color.
Should contain an array with 3 or 4 values (RGB or RGBA) from 0 to 255.
- This attribute cascades.
@attrib color
--]]--
Attribute.color = {}
function Attribute.color.set (widget, value)
widget.attributes.color = value
widget.textData = nil
end
Attribute.color.get = cascade
--[[--
Text and icon alignment.
Should contain a string defining vertical and horizontal alignment.
Vertical alignment is defined by either 'top', 'middle', or 'bottom',
and horizontal alignment is defined by either 'left', 'center', or 'right'.
For example, `align = 'top left'`
- This attribute cascades.
@attrib align
--]]--
Attribute.align = {}
function Attribute.align.set (widget, value)
widget.attributes.align = value
widget.textData = nil
end
Attribute.align.get = cascade
--[[--
Wrap text onto multiple lines.
Should contain `true` for multiline text, or `false` or `nil`
for a single line. Even text containing line breaks will display
as a single line when this attribute is not set to `true`.
- This attribute cascades.
@attrib wrap
--]]--
Attribute.wrap = {}
function Attribute.wrap.set (widget, value)
widget.attributes.wrap = value
widget.textData = nil
end
Attribute.wrap.get = cascade
--[[--
Visual Attributes.
@section visual
--]]--
--[[--
Background color.
Should contain an array with 3 or 4 values (RGB or RGBA) from 0 to 255.
@attrib background
--]]--
Attribute.background = {}
--[[--
Outline color.
Should contain an array with 3 or 4 values (RGB or RGBA) from 0 to 255.
@attrib outline
--]]--
Attribute.outline = {}
--[[--
Slice image.
Should contain a path to an image with "slices" to display for this widget.
@attrib slices
--]]--
Attribute.slices = {}
--[[--
Margin size.
The margin area occupies space outside of the `outline` and `slices`.
@attrib margin
--]]--
Attribute.margin = {}
function Attribute.margin.set (widget, value)
widget.attributes.margin = value
widget.textData = nil
widget:reshape()
end
--[[--
Padding size.
The padding area occupies space inside the `outline` and `slices`,
and outside the space where the `icon` and `text` and any
child widgets appear.
@attrib padding
--]]--
Attribute.padding = {}
function Attribute.padding.set (widget, value)
widget.attributes.padding = value
widget.textData = nil
widget:reshape()
end
--[[--
Icon path.
Should contain a path to an image file.
@attrib icon
--]]--
Attribute.icon = {}
function Attribute.icon.set (widget, value)
widget.attributes.icon = value
widget.textData = nil
end
return Attribute

View File

@ -1,27 +0,0 @@
local ROOT = (...):gsub('[^.]*$', '')
local Backend
if _G.love and (_G.love._version_major or _G.love._version_minor) then
Backend = require(ROOT .. 'backend.love')
else
Backend = require(ROOT .. 'backend.ffisdl')
end
Backend.intersectScissor = Backend.intersectScissor or function (x, y, w, h)
local sx, sy, sw, sh = Backend.getScissor()
if not sx then
return Backend.setScissor(x, y, w, h)
end
local x1 = math.max(sx, x)
local y1 = math.max(sy, y)
local x2 = math.min(sx + sw, x + w)
local y2 = math.min(sy + sh, y + h)
if x2 > x1 and y2 > y1 then
Backend.setScissor(x1, y1, x2 - x1, y2 - y1)
else
-- HACK
Backend.setScissor(-100, -100, 1, 1)
end
end
return Backend

View File

@ -1,540 +0,0 @@
local ROOT = (...):gsub('[^.]*.[^.]*$', '')
local Hooker = require(ROOT .. 'hooker')
local ffi = require 'ffi'
local sdl = require((...) .. '.sdl')
local Image = require((...) .. '.image')
local Font = require((...) .. '.font')
local Keyboard = require((...) .. '.keyboard')
local Text = require((...) .. '.text')
local IntOut = ffi.typeof 'int[1]'
local stack = {}
-- create window and renderer
sdl.enableScreenSaver()
local window = sdl.createWindow('',
sdl.WINDOWPOS_CENTERED, sdl.WINDOWPOS_CENTERED, 800, 600,
sdl.WINDOW_SHOWN + sdl.WINDOW_RESIZABLE)
if window == nil then
error(ffi.string(sdl.getError()))
end
ffi.gc(window, sdl.destroyWindow)
local renderer = sdl.createRenderer(window, -1,
sdl.RENDERER_ACCELERATED + sdl.RENDERER_PRESENTVSYNC)
if renderer == nil then
error(ffi.string(sdl.getError()))
end
ffi.gc(renderer, sdl.destroyRenderer)
sdl.setRenderDrawBlendMode(renderer, sdl.BLENDMODE_BLEND)
local Backend = {}
Backend.sdl = sdl
Backend.isMac = function ()
return sdl.getPlatform() == 'Mac OS X'
end
local callback = {
draw = function () end,
resize = function () end,
mousepressed = function () end,
mousereleased = function () end,
mousemoved = function () end,
keypressed = function () end,
keyreleased = function () end,
textinput = function () end,
wheelmoved = function () end,
}
Backend.run = function ()
local event = sdl.Event()
local tickInterval = 16 -- ~60 fps (with room)
local nextTick = 0
local sdl = sdl
while true do
sdl.pumpEvents()
while sdl.pollEvent(event) ~= 0 do
if event.type == sdl.QUIT then
return
elseif event.type == sdl.WINDOWEVENT
and event.window.event == sdl.WINDOWEVENT_RESIZED then
local window = event.window
callback.resize(window.data1, window.data2)
elseif event.type == sdl.MOUSEBUTTONDOWN then
local button = event.button
callback.mousepressed(button.x, button.y, button.button)
elseif event.type == sdl.MOUSEBUTTONUP then
local button = event.button
callback.mousereleased(button.x, button.y, button.button)
elseif event.type == sdl.MOUSEMOTION then
local motion = event.motion
callback.mousemoved(motion.x, motion.y)
elseif event.type == sdl.KEYDOWN then
local key = Keyboard.stringByKeycode[event.key.keysym.sym] or 'unknown'
local scanCode = Keyboard.stringByScancode[event.key.keysym.scancode] or 'unknown'
callback.keypressed(key, scanCode, event.key['repeat'])
elseif event.type == sdl.KEYUP then
local key = Keyboard.stringByKeycode[event.key.keysym.sym] or 'unknown'
local scanCode = Keyboard.stringByScancode[event.key.keysym.scancode] or 'unknown'
callback.keyreleased(key, scanCode, event.key['repeat'])
elseif event.type == sdl.TEXTINPUT then
callback.textinput(ffi.string(event.text.text))
elseif event.type == sdl.MOUSEWHEEL then
local wheel = event.wheel
callback.wheelmoved(wheel.x, wheel.y)
end
end
sdl.renderSetClipRect(renderer, nil)
sdl.setRenderDrawColor(renderer, 0, 0, 0, 255)
sdl.renderClear(renderer)
callback.draw()
local now = sdl.getTicks()
if nextTick > now then
sdl.delay(nextTick - now)
end
nextTick = now + tickInterval
sdl.renderPresent(renderer)
end
end
Backend.Cursor = function (image, x, y)
return sdl.createColorCursor(image.sdlSurface, x, y)
end
Backend.Font = Font
Backend.Image = function (path)
return Image(renderer, path)
end
Backend.Text = function (...)
return Text(renderer, ...)
end
Backend.Quad = function (x, y, w, h)
return { x, y, w, h }
end
Backend.SpriteBatch = require((...) .. '.spritebatch')
Backend.draw = function (drawable, x, y, sx, sy)
if drawable.draw then
return drawable:draw(x, y, sx, sy)
end
if drawable.sdlTexture == nil
or drawable.sdlRenderer == nil
or drawable.getWidth == nil
or drawable.getHeight == nil
then return
end
local w = drawable:getWidth() * (sx or 1)
local h = drawable:getHeight() * (sy or 1)
-- HACK. Somehow drawing something first prevents renderCopy from
-- incorrectly scaling up in some cases (after rendering slices).
-- For example http://stackoverflow.com/questions/28218906
sdl.renderDrawPoint(drawable.sdlRenderer, -1, -1)
-- Draw the image.
sdl.renderCopy(drawable.sdlRenderer, drawable.sdlTexture,
nil, sdl.Rect(x, y, w, h))
end
Backend.drawRectangle = function (mode, x, y, w, h)
if mode == 'fill' then
sdl.renderFillRect(renderer, sdl.Rect(x, y, w, h))
else
sdl.renderDrawRect(renderer, sdl.Rect(x, y, w, h))
end
end
local currentFont = Font()
local lastColor
-- print( text, x, y, r, sx, sy, ox, oy, kx, ky )
Backend.print = function (text, x, y)
if not text or text == '' then return end
local font = currentFont.sdlFont
local color = sdl.Color(lastColor or { 0, 0, 0, 255 })
local write = Font.SDL2_ttf.TTF_RenderUTF8_Blended
local surface = write(font, text, color)
ffi.gc(surface, sdl.freeSurface)
local texture = sdl.createTextureFromSurface(renderer, surface)
ffi.gc(texture, sdl.destroyTexture)
sdl.renderCopy(renderer, texture, nil, sdl.Rect(x, y, surface.w, surface.h))
end
Backend.getClipboardText = function ()
return ffi.string(sdl.getClipboardText())
end
Backend.setClipboardText = sdl.setClipboardText
Backend.getMousePosition = function ()
local x, y = IntOut(), IntOut()
sdl.getMouseState(x, y)
return x[0], y[0]
end
local function SystemCursor (id)
return ffi.gc(sdl.createSystemCursor(id), sdl.freeCursor)
end
local systemCursors = {
arrow = SystemCursor(sdl.SYSTEM_CURSOR_ARROW),
ibeam = SystemCursor(sdl.SYSTEM_CURSOR_IBEAM),
wait = SystemCursor(sdl.SYSTEM_CURSOR_WAIT),
crosshair = SystemCursor(sdl.SYSTEM_CURSOR_CROSSHAIR),
waitarrow = SystemCursor(sdl.SYSTEM_CURSOR_WAITARROW),
sizenwse = SystemCursor(sdl.SYSTEM_CURSOR_SIZENWSE),
sizenesw = SystemCursor(sdl.SYSTEM_CURSOR_SIZENESW),
sizewe = SystemCursor(sdl.SYSTEM_CURSOR_SIZEWE),
sizens = SystemCursor(sdl.SYSTEM_CURSOR_SIZENS),
sizeall = SystemCursor(sdl.SYSTEM_CURSOR_SIZEALL),
no = SystemCursor(sdl.SYSTEM_CURSOR_NO),
hand = SystemCursor(sdl.SYSTEM_CURSOR_HAND),
}
Backend.getSystemCursor = function (name)
return systemCursors[name] or systemCursors.arrow
end
Backend.getWindowSize = function ()
local x, y = IntOut(), IntOut()
sdl.getWindowSize(window, x, y)
return x[0], y[0]
end
Backend.getTime = function ()
return sdl.getTicks() * 0.001
end
Backend.isKeyDown = function (...)
local state = sdl.getKeyboardState(nil)
for i = 1, select('#', ...) do
local name = select(i, ...)
local scan = Keyboard.scancodeByString[name]
if scan and state[scan] ~= 0 then
return true
end
end
return false
end
Backend.isMouseDown = function ()
end
Backend.quit = function ()
sdl.quit()
os.exit()
end
Backend.setColor = function (color)
lastColor = color
sdl.setRenderDrawColor(renderer,
color[1], color[2], color[3], color[4] or 255)
end
Backend.setCursor = function (cursor)
sdl.setCursor(cursor or Backend.getSystemCursor('arrow'))
end
Backend.setFont = function (font)
currentFont = font
end
local lastScissor
Backend.setScissor = function (x, y, w, h)
-- y = y and Backend.getWindowHeight() - (y + h)
lastScissor = x and sdl.Rect(x, y, w, h)
sdl.renderSetClipRect(renderer, lastScissor)
end
Backend.getScissor = function ()
if lastScissor ~= nil then
local x, y = lastScissor.x, lastScissor.y
local w, h = lastScissor.w, lastScissor.h
-- y = y and Backend.getWindowHeight() - (y + h)
return x, y, w, h
end
end
function Backend.hide (layout)
for _, item in ipairs(layout.hooks) do
Hooker.unhook(item)
end
layout.hooks = {}
end
local function hook (layout, key, method, hookLast)
layout.hooks[#layout.hooks + 1] = Hooker.hook(
callback, key, method, hookLast)
end
Backend.pop = function ()
local history = stack[#stack]
lastColor = history.color or { 0, 0, 0, 255 }
lastScissor = history.scissor
sdl.setRenderDrawColor(renderer,
lastColor[1], lastColor[2], lastColor[3], lastColor[4] or 255)
sdl.renderSetClipRect(renderer, lastScissor) -- Backend.setScissor(history.scissor)
stack[#stack] = nil
end
Backend.push = function ()
stack[#stack + 1] = {
color = lastColor,
scissor = lastScissor,
}
end
local isMouseDown = function ()
return sdl.getMouseState(nil, nil) > 0
end
local buttonIds = {
[sdl.BUTTON_LEFT] = 'left',
[sdl.BUTTON_MIDDLE] = 'middle',
[sdl.BUTTON_RIGHT] = 'right',
-- [sdl.BUTTON_X1] = 'x1',
-- [sdl.BUTTON_X2] = 'x2',
}
local function getMouseButtonId (value)
return value and buttonIds[value] or value
end
function Backend.show (layout)
local input = layout.input
hook(layout, 'draw', function ()
input:handleDisplay(layout)
end, true)
hook(layout, 'resize', function (width, height)
return input:handleReshape(layout, width, height)
end)
hook(layout, 'mousepressed', function (x, y, button)
return input:handlePressStart(layout, getMouseButtonId(button), x, y)
end)
hook(layout, 'mousereleased', function (x, y, button)
return input:handlePressEnd(layout, getMouseButtonId(button), x, y)
end)
hook(layout, 'mousemoved', function (x, y, dx, dy)
if isMouseDown() then
return input:handlePressedMove(layout, x, y)
else
return input:handleMove(layout, x, y)
end
end)
hook(layout, 'keypressed', function (key, scanCode, isRepeat)
return input:handleKeyPress(layout, key, scanCode, Backend.getMousePosition())
end)
hook(layout, 'keyreleased', function (key, scanCode)
return input:handleKeyRelease(layout, key, scanCode, Backend.getMousePosition())
end)
hook(layout, 'textinput', function (text)
return input:handleTextInput(layout, text, Backend.getMousePosition())
end)
hook(layout, 'wheelmoved', function (x, y)
return input:handleWheelMove(layout, x, y)
end)
end
function Backend.getWindowMaximized ()
local flags = sdl.getWindowFlags(window)
return bit.band(flags, sdl.WINDOW_MAXIMIZED) ~= 0
end
function Backend.setWindowMaximized (maximized)
if maximized then
sdl.maximizeWindow(window)
else
sdl.restoreWindow(window)
end
end
function Backend.getWindowMinimized ()
local flags = sdl.getWindowFlags(window)
return bit.band(flags, sdl.WINDOW_MINIMIZED) ~= 0
end
function Backend.setWindowMinimized (minimized)
if minimized then
sdl.minimizeWindow(window)
else
sdl.restoreWindow(window)
end
end
function Backend.getWindowBorderless ()
local flags = sdl.getWindowFlags(window)
return bit.band(flags, sdl.WINDOW_BORDERLESS) ~= 0
end
function Backend.setWindowBorderless (borderless)
return sdl.setWindowBordered(window, not borderless)
end
function Backend.getWindowFullscreen ()
local flags = sdl.getWindowFlags(window)
return bit.band(flags, sdl.WINDOW_FULLSCREEN) ~= 0
end
function Backend.setWindowFullscreen (fullscreen)
return sdl.setWindowFullscreen(window, not not fullscreen)
end
function Backend.getWindowGrab ()
return sdl.getWindowGrab(window)
end
function Backend.setWindowGrab (grab)
return sdl.setWindowGrab(window, not not grab)
end
local SDL2_image = ffi.load 'SDL2_image'
function Backend.setWindowIcon (icon)
-- XXX: is it safe to free this?
local surface = ffi.gc(SDL2_image.IMG_Load(icon), sdl.freeSurface)
if surface == nil then
error(ffi.string(sdl.getError()))
end
sdl.setWindowIcon(window, surface)
end
function Backend.getWindowMaxwidth ()
local w, h = IntOut(), IntOut()
sdl.getWindowMaximumSize(window, w, h)
return w[0]
end
function Backend.setWindowMaxwidth (maxwidth)
local w, h = IntOut(), IntOut()
sdl.getWindowMaximumSize(window, w, h)
sdl.setWindowMaximumSize(window, maxwidth, h[0] or 16384)
end
function Backend.getWindowMaxheight ()
local w, h = IntOut(), IntOut()
sdl.getWindowMaximumSize(window, w, h)
return h[0]
end
function Backend.setWindowMaxheight (maxheight)
local w, h = IntOut(), IntOut()
sdl.getWindowMaximumSize(window, w, h)
sdl.setWindowMaximumSize(window, w[0] or 16384, maxheight)
end
function Backend.getWindowMinwidth ()
local w, h = IntOut(), IntOut()
sdl.getWindowMinimumSize(window, w, h)
return w[0]
end
function Backend.setWindowMinwidth (minwidth)
local w, h = IntOut(), IntOut()
sdl.getWindowMinimumSize(window, w, h)
sdl.setWindowMinimumSize(window, minwidth, h[0] or 0)
end
function Backend.getWindowMinheight ()
local w, h = IntOut(), IntOut()
sdl.getWindowMinimumSize(window, w, h)
return h[0]
end
function Backend.setWindowMinheight (minheight)
local w, h = IntOut(), IntOut()
sdl.getWindowMinimumSize(window, w, h)
sdl.setWindowMinimumSize(window, w[0] or 0, minheight)
end
function Backend.getWindowTop ()
local x, y = IntOut(), IntOut()
sdl.getWindowPosition(window, x, y)
return y[0]
end
function Backend.setWindowTop (top)
local x, y = IntOut(), IntOut()
sdl.getWindowPosition(window, x, y)
sdl.setWindowPosition(window, x[0] or 0, top)
end
function Backend.getWindowLeft ()
local x, y = IntOut(), IntOut()
sdl.getWindowPosition(window, x, y)
return x[0]
end
function Backend.setWindowLeft (left)
local x, y = IntOut(), IntOut()
sdl.getWindowPosition(window, x, y)
sdl.setWindowPosition(window, left, y[0] or 0)
end
function Backend.getWindowWidth ()
local w, h = IntOut(), IntOut()
sdl.getWindowSize(window, w, h)
return w[0]
end
function Backend.setWindowWidth (width)
local w, h = IntOut(), IntOut()
sdl.getWindowSize(window, w, h)
sdl.setWindowSize(window, width, h[0] or 600)
end
function Backend.getWindowHeight ()
local w, h = IntOut(), IntOut()
sdl.getWindowSize(window, w, h)
return h[0]
end
function Backend.setWindowHeight (height)
local w, h = IntOut(), IntOut()
sdl.getWindowSize(window, w, h)
sdl.setWindowSize(window, w[0] or 800, height)
end
function Backend.getWindowTitle (title)
return sdl.getWindowTitle(window)
end
function Backend.setWindowTitle (title)
sdl.setWindowTitle(window, title)
end
return Backend

View File

@ -1,249 +0,0 @@
local REL = (...):gsub('[^.]*$', '')
local APP_ROOT = rawget(_G, 'LUIGI_APP_ROOT') or ''
local ffi = require 'ffi'
local sdl = require(REL .. 'sdl')
local SDL2_ttf = ffi.load 'SDL2_ttf'
local IntOut = ffi.typeof 'int[1]'
ffi.cdef [[
/* The internal structure containing font information */
typedef struct _TTF_Font TTF_Font;
/* Initialize the TTF engine - returns 0 if successful, -1 on error */
int TTF_Init(void);
/* Open a font file and create a font of the specified point size.
* Some .fon fonts will have several sizes embedded in the file, so the
* point size becomes the index of choosing which size. If the value
* is too high, the last indexed size will be the default. */
TTF_Font *TTF_OpenFont(const char *file, int ptsize);
TTF_Font *TTF_OpenFontIndex(const char *file, int ptsize, long index);
TTF_Font *TTF_OpenFontRW(SDL_RWops *src, int freesrc, int ptsize);
TTF_Font *TTF_OpenFontIndexRW(SDL_RWops *src, int freesrc, int ptsize, long index);
/* Set and retrieve the font style
#define TTF_STYLE_NORMAL 0x00
#define TTF_STYLE_BOLD 0x01
#define TTF_STYLE_ITALIC 0x02
#define TTF_STYLE_UNDERLINE 0x04
#define TTF_STYLE_STRIKETHROUGH 0x08
*/
int TTF_GetFontStyle(const TTF_Font *font);
void TTF_SetFontStyle(TTF_Font *font, int style);
int TTF_GetFontOutline(const TTF_Font *font);
void TTF_SetFontOutline(TTF_Font *font, int outline);
/* Set and retrieve FreeType hinter settings
#define TTF_HINTING_NORMAL 0
#define TTF_HINTING_LIGHT 1
#define TTF_HINTING_MONO 2
#define TTF_HINTING_NONE 3
*/
int TTF_GetFontHinting(const TTF_Font *font);
void TTF_SetFontHinting(TTF_Font *font, int hinting);
/* Get the total height of the font - usually equal to point size */
int TTF_FontHeight(const TTF_Font *font);
/* Get the offset from the baseline to the top of the font
This is a positive value, relative to the baseline.
*/
int TTF_FontAscent(const TTF_Font *font);
/* Get the offset from the baseline to the bottom of the font
This is a negative value, relative to the baseline.
*/
int TTF_FontDescent(const TTF_Font *font);
/* Get the recommended spacing between lines of text for this font */
int TTF_FontLineSkip(const TTF_Font *font);
/* Get/Set whether or not kerning is allowed for this font */
int TTF_GetFontKerning(const TTF_Font *font);
void TTF_SetFontKerning(TTF_Font *font, int allowed);
/* Get the number of faces of the font */
long TTF_FontFaces(const TTF_Font *font);
/* Get the font face attributes, if any */
int TTF_FontFaceIsFixedWidth(const TTF_Font *font);
char *TTF_FontFaceFamilyName(const TTF_Font *font);
char *TTF_FontFaceStyleName(const TTF_Font *font);
/* Check wether a glyph is provided by the font or not */
int TTF_GlyphIsProvided(const TTF_Font *font, Uint16 ch);
/* Get the metrics (dimensions) of a glyph
To understand what these metrics mean, here is a useful link:
http://freetype.sourceforge.net/freetype2/docs/tutorial/step2.html
*/
int TTF_GlyphMetrics(TTF_Font *font, Uint16 ch,
int *minx, int *maxx,
int *miny, int *maxy, int *advance);
/* Get the dimensions of a rendered string of text */
int TTF_SizeText(TTF_Font *font, const char *text, int *w, int *h);
int TTF_SizeUTF8(TTF_Font *font, const char *text, int *w, int *h);
int TTF_SizeUTF8_Wrapped(TTF_Font *font, const char *text, int wrapLength, int *w, int *h, int *lineCount);
int TTF_SizeUNICODE(TTF_Font *font, const Uint16 *text, int *w, int *h);
/* Create an 8-bit palettized surface and render the given text at
fast quality with the given font and color. The 0 pixel is the
colorkey, giving a transparent background, and the 1 pixel is set
to the text color.
This function returns the new surface, or NULL if there was an error.
*/
SDL_Surface *TTF_RenderText_Solid(TTF_Font *font,
const char *text, SDL_Color fg);
SDL_Surface *TTF_RenderUTF8_Solid(TTF_Font *font,
const char *text, SDL_Color fg);
SDL_Surface *TTF_RenderUNICODE_Solid(TTF_Font *font,
const Uint16 *text, SDL_Color fg);
/* Create an 8-bit palettized surface and render the given glyph at
fast quality with the given font and color. The 0 pixel is the
colorkey, giving a transparent background, and the 1 pixel is set
to the text color. The glyph is rendered without any padding or
centering in the X direction, and aligned normally in the Y direction.
This function returns the new surface, or NULL if there was an error.
*/
SDL_Surface *TTF_RenderGlyph_Solid(TTF_Font *font,
Uint16 ch, SDL_Color fg);
/* Create an 8-bit palettized surface and render the given text at
high quality with the given font and colors. The 0 pixel is background,
while other pixels have varying degrees of the foreground color.
This function returns the new surface, or NULL if there was an error.
*/
SDL_Surface *TTF_RenderText_Shaded(TTF_Font *font,
const char *text, SDL_Color fg, SDL_Color bg);
SDL_Surface *TTF_RenderUTF8_Shaded(TTF_Font *font,
const char *text, SDL_Color fg, SDL_Color bg);
SDL_Surface *TTF_RenderUNICODE_Shaded(TTF_Font *font,
const Uint16 *text, SDL_Color fg, SDL_Color bg);
/* Create an 8-bit palettized surface and render the given glyph at
high quality with the given font and colors. The 0 pixel is background,
while other pixels have varying degrees of the foreground color.
The glyph is rendered without any padding or centering in the X
direction, and aligned normally in the Y direction.
This function returns the new surface, or NULL if there was an error.
*/
SDL_Surface *TTF_RenderGlyph_Shaded(TTF_Font *font,
Uint16 ch, SDL_Color fg, SDL_Color bg);
/* Create a 32-bit ARGB surface and render the given text at high quality,
using alpha blending to dither the font with the given color.
This function returns the new surface, or NULL if there was an error.
*/
SDL_Surface *TTF_RenderText_Blended(TTF_Font *font,
const char *text, SDL_Color fg);
SDL_Surface *TTF_RenderUTF8_Blended(TTF_Font *font,
const char *text, SDL_Color fg);
SDL_Surface *TTF_RenderUNICODE_Blended(TTF_Font *font,
const Uint16 *text, SDL_Color fg);
/* Create a 32-bit ARGB surface and render the given text at high quality,
using alpha blending to dither the font with the given color.
Text is wrapped to multiple lines on line endings and on word boundaries
if it extends beyond wrapLength in pixels.
This function returns the new surface, or NULL if there was an error.
*/
SDL_Surface *TTF_RenderText_Blended_Wrapped(TTF_Font *font,
const char *text, SDL_Color fg, Uint32 wrapLength);
SDL_Surface *TTF_RenderUTF8_Blended_Wrapped(TTF_Font *font,
const char *text, SDL_Color fg, Uint32 wrapLength);
SDL_Surface *TTF_RenderUNICODE_Blended_Wrapped(TTF_Font *font,
const Uint16 *text, SDL_Color fg, Uint32 wrapLength);
/* Create a 32-bit ARGB surface and render the given glyph at high quality,
using alpha blending to dither the font with the given color.
The glyph is rendered without any padding or centering in the X
direction, and aligned normally in the Y direction.
This function returns the new surface, or NULL if there was an error.
*/
SDL_Surface *TTF_RenderGlyph_Blended(TTF_Font *font,
Uint16 ch, SDL_Color fg);
/* Close an opened font file */
void TTF_CloseFont(TTF_Font *font);
/* De-initialize the TTF engine */
void TTF_Quit(void);
/* Check if the TTF engine is initialized */
int TTF_WasInit(void);
/* Get the kerning size of two glyphs */
int TTF_GetFontKerningSize(TTF_Font *font, int prev_index, int index);
]]
if SDL2_ttf.TTF_Init() ~= 0 then
error(ffi.string(sdl.getError()))
end
local Font = setmetatable({}, { __call = function (self, ...)
local object = setmetatable({}, { __index = self })
return object, self.constructor(object, ...)
end })
Font.SDL2_ttf = SDL2_ttf
local fontCache = {}
function Font:constructor (path, size)
if not size then
size = 12
end
if not path then
path = REL:gsub('%.', '/') .. 'resource/DejaVuSans.ttf'
end
local key = path .. '_' .. size
if not fontCache[key] then
local font = SDL2_ttf.TTF_OpenFont(APP_ROOT .. path, size)
if font == nil then
error(ffi.string(sdl.getError()))
end
fontCache[key] = font
end
self.sdlFont = fontCache[key]
end
function Font:setAlignment (align)
self.align = align
end
function Font:setWidth (width)
self.width = width
end
function Font:getLineHeight ()
return SDL2_ttf.TTF_FontHeight(self.sdlFont)
end
function Font:getAscender ()
return SDL2_ttf.TTF_FontAscent(self.sdlFont)
end
function Font:getDescender ()
return SDL2_ttf.TTF_FontDescent(self.sdlFont)
end
function Font:getAdvance (text)
local w, h = IntOut(), IntOut()
SDL2_ttf.TTF_SizeUTF8(self.sdlFont, text, w, h)
return w[0]
end
return Font

View File

@ -1,46 +0,0 @@
local REL = (...):gsub('[^.]*$', '')
local APP_ROOT = rawget(_G, 'LUIGI_APP_ROOT') or ''
local ffi = require 'ffi'
local sdl = require(REL .. 'sdl')
local SDL2_image = ffi.load 'SDL2_image'
ffi.cdef [[ SDL_Surface *IMG_Load(const char *file); ]]
local Image = setmetatable({}, { __call = function (self, ...)
local object = setmetatable({}, { __index = self })
return object, self.constructor(object, ...)
end })
function Image:constructor (renderer, path)
self.sdlRenderer = renderer
self.sdlSurface = ffi.gc(
SDL2_image.IMG_Load(APP_ROOT .. path),
sdl.freeSurface)
if self.sdlSurface == nil then
error(ffi.string(sdl.getError()))
end
self.sdlTexture = ffi.gc(
sdl.createTextureFromSurface(renderer, self.sdlSurface),
sdl.destroyTexture)
if self.sdlTexture == nil then
error(ffi.string(sdl.getError()))
end
self.width = self.sdlSurface.w
self.height = self.sdlSurface.h
end
function Image:getWidth ()
return self.width
end
function Image:getHeight ()
return self.height
end
return Image

View File

@ -1,494 +0,0 @@
local REL = (...):gsub('[^.]*$', '')
local sdl = require(REL .. 'sdl')
local Keyboard = {
scancodeByString = {},
stringByScancode = {},
keycodeByString = {},
stringByKeycode = {},
}
local function registerScancodes (registry)
for _, entry in ipairs(registry) do
Keyboard.scancodeByString[entry[1]] = entry[2]
Keyboard.stringByScancode[entry[2]] = entry[1]
end
end
local function registerKeycodes (registry)
for _, entry in ipairs(registry) do
Keyboard.keycodeByString[entry[1]] = entry[2]
Keyboard.stringByKeycode[entry[2]] = entry[1]
end
end
registerScancodes {
{ "unknown", sdl.SCANCODE_UNKNOWN },
{ "a", sdl.SCANCODE_A },
{ "b", sdl.SCANCODE_B },
{ "c", sdl.SCANCODE_C },
{ "d", sdl.SCANCODE_D },
{ "e", sdl.SCANCODE_E },
{ "f", sdl.SCANCODE_F },
{ "g", sdl.SCANCODE_G },
{ "h", sdl.SCANCODE_H },
{ "i", sdl.SCANCODE_I },
{ "j", sdl.SCANCODE_J },
{ "k", sdl.SCANCODE_K },
{ "l", sdl.SCANCODE_L },
{ "m", sdl.SCANCODE_M },
{ "n", sdl.SCANCODE_N },
{ "o", sdl.SCANCODE_O },
{ "p", sdl.SCANCODE_P },
{ "q", sdl.SCANCODE_Q },
{ "r", sdl.SCANCODE_R },
{ "s", sdl.SCANCODE_S },
{ "t", sdl.SCANCODE_T },
{ "u", sdl.SCANCODE_U },
{ "v", sdl.SCANCODE_V },
{ "w", sdl.SCANCODE_W },
{ "x", sdl.SCANCODE_X },
{ "y", sdl.SCANCODE_Y },
{ "z", sdl.SCANCODE_Z },
{ "1", sdl.SCANCODE_1 },
{ "2", sdl.SCANCODE_2 },
{ "3", sdl.SCANCODE_3 },
{ "4", sdl.SCANCODE_4 },
{ "5", sdl.SCANCODE_5 },
{ "6", sdl.SCANCODE_6 },
{ "7", sdl.SCANCODE_7 },
{ "8", sdl.SCANCODE_8 },
{ "9", sdl.SCANCODE_9 },
{ "0", sdl.SCANCODE_0 },
{ "return", sdl.SCANCODE_RETURN },
{ "escape", sdl.SCANCODE_ESCAPE },
{ "backspace", sdl.SCANCODE_BACKSPACE },
{ "tab", sdl.SCANCODE_TAB },
{ "space", sdl.SCANCODE_SPACE },
{ "-", sdl.SCANCODE_MINUS },
{ "=", sdl.SCANCODE_EQUALS },
{ "[", sdl.SCANCODE_LEFTBRACKET },
{ "]", sdl.SCANCODE_RIGHTBRACKET },
{ "\\", sdl.SCANCODE_BACKSLASH },
{ "nonus#", sdl.SCANCODE_NONUSHASH },
{ ";", sdl.SCANCODE_SEMICOLON },
{ "'", sdl.SCANCODE_APOSTROPHE },
{ "`", sdl.SCANCODE_GRAVE },
{ ",", sdl.SCANCODE_COMMA },
{ ".", sdl.SCANCODE_PERIOD },
{ "/", sdl.SCANCODE_SLASH },
{ "capslock", sdl.SCANCODE_CAPSLOCK },
{ "f1", sdl.SCANCODE_F1 },
{ "f2", sdl.SCANCODE_F2 },
{ "f3", sdl.SCANCODE_F3 },
{ "f4", sdl.SCANCODE_F4 },
{ "f5", sdl.SCANCODE_F5 },
{ "f6", sdl.SCANCODE_F6 },
{ "f7", sdl.SCANCODE_F7 },
{ "f8", sdl.SCANCODE_F8 },
{ "f9", sdl.SCANCODE_F9 },
{ "f10", sdl.SCANCODE_F10 },
{ "f11", sdl.SCANCODE_F11 },
{ "f12", sdl.SCANCODE_F12 },
{ "printscreen", sdl.SCANCODE_PRINTSCREEN },
{ "scrolllock", sdl.SCANCODE_SCROLLLOCK },
{ "pause", sdl.SCANCODE_PAUSE },
{ "insert", sdl.SCANCODE_INSERT },
{ "home", sdl.SCANCODE_HOME },
{ "pageup", sdl.SCANCODE_PAGEUP },
{ "delete", sdl.SCANCODE_DELETE },
{ "end", sdl.SCANCODE_END },
{ "pagedown", sdl.SCANCODE_PAGEDOWN },
{ "right", sdl.SCANCODE_RIGHT },
{ "left", sdl.SCANCODE_LEFT },
{ "down", sdl.SCANCODE_DOWN },
{ "up", sdl.SCANCODE_UP },
{ "numlock", sdl.SCANCODE_NUMLOCKCLEAR },
{ "kp/", sdl.SCANCODE_KP_DIVIDE },
{ "kp*", sdl.SCANCODE_KP_MULTIPLY },
{ "kp-", sdl.SCANCODE_KP_MINUS },
{ "kp+", sdl.SCANCODE_KP_PLUS },
{ "kpenter", sdl.SCANCODE_KP_ENTER },
{ "kp1", sdl.SCANCODE_KP_1 },
{ "kp2", sdl.SCANCODE_KP_2 },
{ "kp3", sdl.SCANCODE_KP_3 },
{ "kp4", sdl.SCANCODE_KP_4 },
{ "kp5", sdl.SCANCODE_KP_5 },
{ "kp6", sdl.SCANCODE_KP_6 },
{ "kp7", sdl.SCANCODE_KP_7 },
{ "kp8", sdl.SCANCODE_KP_8 },
{ "kp9", sdl.SCANCODE_KP_9 },
{ "kp0", sdl.SCANCODE_KP_0 },
{ "kp.", sdl.SCANCODE_KP_PERIOD },
{ "nonusbackslash", sdl.SCANCODE_NONUSBACKSLASH },
{ "application", sdl.SCANCODE_APPLICATION },
{ "power", sdl.SCANCODE_POWER },
{ "=", sdl.SCANCODE_KP_EQUALS },
{ "f13", sdl.SCANCODE_F13 },
{ "f14", sdl.SCANCODE_F14 },
{ "f15", sdl.SCANCODE_F15 },
{ "f16", sdl.SCANCODE_F16 },
{ "f17", sdl.SCANCODE_F17 },
{ "f18", sdl.SCANCODE_F18 },
{ "f19", sdl.SCANCODE_F19 },
{ "f20", sdl.SCANCODE_F20 },
{ "f21", sdl.SCANCODE_F21 },
{ "f22", sdl.SCANCODE_F22 },
{ "f23", sdl.SCANCODE_F23 },
{ "f24", sdl.SCANCODE_F24 },
{ "execute", sdl.SCANCODE_EXECUTE },
{ "help", sdl.SCANCODE_HELP },
{ "menu", sdl.SCANCODE_MENU },
{ "select", sdl.SCANCODE_SELECT },
{ "stop", sdl.SCANCODE_STOP },
{ "again", sdl.SCANCODE_AGAIN },
{ "undo", sdl.SCANCODE_UNDO },
{ "cut", sdl.SCANCODE_CUT },
{ "copy", sdl.SCANCODE_COPY },
{ "paste", sdl.SCANCODE_PASTE },
{ "find", sdl.SCANCODE_FIND },
{ "mute", sdl.SCANCODE_MUTE },
{ "volumeup", sdl.SCANCODE_VOLUMEUP },
{ "volumedown", sdl.SCANCODE_VOLUMEDOWN },
{ "kp,", sdl.SCANCODE_KP_COMMA },
{ "kp=400", sdl.SCANCODE_KP_EQUALSAS400 },
{ "international1", sdl.SCANCODE_INTERNATIONAL1 },
{ "international2", sdl.SCANCODE_INTERNATIONAL2 },
{ "international3", sdl.SCANCODE_INTERNATIONAL3 },
{ "international4", sdl.SCANCODE_INTERNATIONAL4 },
{ "international5", sdl.SCANCODE_INTERNATIONAL5 },
{ "international6", sdl.SCANCODE_INTERNATIONAL6 },
{ "international7", sdl.SCANCODE_INTERNATIONAL7 },
{ "international8", sdl.SCANCODE_INTERNATIONAL8 },
{ "international9", sdl.SCANCODE_INTERNATIONAL9 },
{ "lang1", sdl.SCANCODE_LANG1 },
{ "lang2", sdl.SCANCODE_LANG2 },
{ "lang3", sdl.SCANCODE_LANG3 },
{ "lang4", sdl.SCANCODE_LANG4 },
{ "lang5", sdl.SCANCODE_LANG5 },
{ "lang6", sdl.SCANCODE_LANG6 },
{ "lang7", sdl.SCANCODE_LANG7 },
{ "lang8", sdl.SCANCODE_LANG8 },
{ "lang9", sdl.SCANCODE_LANG9 },
{ "alterase", sdl.SCANCODE_ALTERASE },
{ "sysreq", sdl.SCANCODE_SYSREQ },
{ "cancel", sdl.SCANCODE_CANCEL },
{ "clear", sdl.SCANCODE_CLEAR },
{ "prior", sdl.SCANCODE_PRIOR },
{ "return2", sdl.SCANCODE_RETURN2 },
{ "separator", sdl.SCANCODE_SEPARATOR },
{ "out", sdl.SCANCODE_OUT },
{ "oper", sdl.SCANCODE_OPER },
{ "clearagain", sdl.SCANCODE_CLEARAGAIN },
{ "crsel", sdl.SCANCODE_CRSEL },
{ "exsel", sdl.SCANCODE_EXSEL },
{ "kp00", sdl.SCANCODE_KP_00 },
{ "kp000", sdl.SCANCODE_KP_000 },
{ "thsousandsseparator", sdl.SCANCODE_THOUSANDSSEPARATOR },
{ "decimalseparator", sdl.SCANCODE_DECIMALSEPARATOR },
{ "currencyunit", sdl.SCANCODE_CURRENCYUNIT },
{ "currencysubunit", sdl.SCANCODE_CURRENCYSUBUNIT },
{ "kp(", sdl.SCANCODE_KP_LEFTPAREN },
{ "kp)", sdl.SCANCODE_KP_RIGHTPAREN },
{ "kp{", sdl.SCANCODE_KP_LEFTBRACE },
{ "kp}", sdl.SCANCODE_KP_RIGHTBRACE },
{ "kptab", sdl.SCANCODE_KP_TAB },
{ "kpbackspace", sdl.SCANCODE_KP_BACKSPACE },
{ "kpa", sdl.SCANCODE_KP_A },
{ "kpb", sdl.SCANCODE_KP_B },
{ "kpc", sdl.SCANCODE_KP_C },
{ "kpd", sdl.SCANCODE_KP_D },
{ "kpe", sdl.SCANCODE_KP_E },
{ "kpf", sdl.SCANCODE_KP_F },
{ "kpxor", sdl.SCANCODE_KP_XOR },
{ "kpower", sdl.SCANCODE_KP_POWER },
{ "kp%", sdl.SCANCODE_KP_PERCENT },
{ "kp<", sdl.SCANCODE_KP_LESS },
{ "kp>", sdl.SCANCODE_KP_GREATER },
{ "kp&", sdl.SCANCODE_KP_AMPERSAND },
{ "kp&&", sdl.SCANCODE_KP_DBLAMPERSAND },
{ "kp|", sdl.SCANCODE_KP_VERTICALBAR },
{ "kp||", sdl.SCANCODE_KP_DBLVERTICALBAR },
{ "kp:", sdl.SCANCODE_KP_COLON },
{ "kp#", sdl.SCANCODE_KP_HASH },
{ "kp ", sdl.SCANCODE_KP_SPACE },
{ "kp@", sdl.SCANCODE_KP_AT },
{ "kp!", sdl.SCANCODE_KP_EXCLAM },
{ "kpmemstore", sdl.SCANCODE_KP_MEMSTORE },
{ "kpmemrecall", sdl.SCANCODE_KP_MEMRECALL },
{ "kpmemclear", sdl.SCANCODE_KP_MEMCLEAR },
{ "kpmem+", sdl.SCANCODE_KP_MEMADD },
{ "kpmem-", sdl.SCANCODE_KP_MEMSUBTRACT },
{ "kpmem*", sdl.SCANCODE_KP_MEMMULTIPLY },
{ "kpmem/", sdl.SCANCODE_KP_MEMDIVIDE },
{ "kp+-", sdl.SCANCODE_KP_PLUSMINUS },
{ "kpclear", sdl.SCANCODE_KP_CLEAR },
{ "kpclearentry", sdl.SCANCODE_KP_CLEARENTRY },
{ "kpbinary", sdl.SCANCODE_KP_BINARY },
{ "kpoctal", sdl.SCANCODE_KP_OCTAL },
{ "kpdecimal", sdl.SCANCODE_KP_DECIMAL },
{ "kphex", sdl.SCANCODE_KP_HEXADECIMAL },
{ "lctrl", sdl.SCANCODE_LCTRL },
{ "lshift", sdl.SCANCODE_LSHIFT },
{ "lalt", sdl.SCANCODE_LALT },
{ "lgui", sdl.SCANCODE_LGUI },
{ "rctrl", sdl.SCANCODE_RCTRL },
{ "rshift", sdl.SCANCODE_RSHIFT },
{ "ralt", sdl.SCANCODE_RALT },
{ "rgui", sdl.SCANCODE_RGUI },
{ "mode", sdl.SCANCODE_MODE },
{ "audionext", sdl.SCANCODE_AUDIONEXT },
{ "audioprev", sdl.SCANCODE_AUDIOPREV },
{ "audiostop", sdl.SCANCODE_AUDIOSTOP },
{ "audioplay", sdl.SCANCODE_AUDIOPLAY },
{ "audiomute", sdl.SCANCODE_AUDIOMUTE },
{ "mediaselect", sdl.SCANCODE_MEDIASELECT },
{ "www", sdl.SCANCODE_WWW },
{ "mail", sdl.SCANCODE_MAIL },
{ "calculator", sdl.SCANCODE_CALCULATOR },
{ "computer", sdl.SCANCODE_COMPUTER },
{ "acsearch", sdl.SCANCODE_AC_SEARCH },
{ "achome", sdl.SCANCODE_AC_HOME },
{ "acback", sdl.SCANCODE_AC_BACK },
{ "acforward", sdl.SCANCODE_AC_FORWARD },
{ "acstop", sdl.SCANCODE_AC_STOP },
{ "acrefresh", sdl.SCANCODE_AC_REFRESH },
{ "acbookmarks", sdl.SCANCODE_AC_BOOKMARKS },
{ "brightnessdown", sdl.SCANCODE_BRIGHTNESSDOWN },
{ "brightnessup", sdl.SCANCODE_BRIGHTNESSUP },
{ "displayswitch", sdl.SCANCODE_DISPLAYSWITCH },
{ "kbdillumtoggle", sdl.SCANCODE_KBDILLUMTOGGLE },
{ "kbdillumdown", sdl.SCANCODE_KBDILLUMDOWN },
{ "kbdillumup", sdl.SCANCODE_KBDILLUMUP },
{ "eject", sdl.SCANCODE_EJECT },
{ "sleep", sdl.SCANCODE_SLEEP },
{ "app1", sdl.SCANCODE_APP1 },
{ "app2", sdl.SCANCODE_APP2 },
}
registerKeycodes {
{ "unknown", sdl.C.SDLK_UNKNOWN },
{ "return", sdl.C.SDLK_RETURN },
{ "escape", sdl.C.SDLK_ESCAPE },
{ "backspace", sdl.C.SDLK_BACKSPACE },
{ "tab", sdl.C.SDLK_TAB },
{ "space", sdl.C.SDLK_SPACE },
{ "!", sdl.C.SDLK_EXCLAIM },
{ "\"", sdl.C.SDLK_QUOTEDBL },
{ "#", sdl.C.SDLK_HASH },
{ "%", sdl.C.SDLK_PERCENT },
{ "$", sdl.C.SDLK_DOLLAR },
{ "&", sdl.C.SDLK_AMPERSAND },
{ "'", sdl.C.SDLK_QUOTE },
{ "(", sdl.C.SDLK_LEFTPAREN },
{ ")", sdl.C.SDLK_RIGHTPAREN },
{ "*", sdl.C.SDLK_ASTERISK },
{ "+", sdl.C.SDLK_PLUS },
{ ",", sdl.C.SDLK_COMMA },
{ "-", sdl.C.SDLK_MINUS },
{ ".", sdl.C.SDLK_PERIOD },
{ "/", sdl.C.SDLK_SLASH },
{ "0", sdl.C.SDLK_0 },
{ "1", sdl.C.SDLK_1 },
{ "2", sdl.C.SDLK_2 },
{ "3", sdl.C.SDLK_3 },
{ "4", sdl.C.SDLK_4 },
{ "5", sdl.C.SDLK_5 },
{ "6", sdl.C.SDLK_6 },
{ "7", sdl.C.SDLK_7 },
{ "8", sdl.C.SDLK_8 },
{ "9", sdl.C.SDLK_9 },
{ ":", sdl.C.SDLK_COLON },
{ ";", sdl.C.SDLK_SEMICOLON },
{ "<", sdl.C.SDLK_LESS },
{ "=", sdl.C.SDLK_EQUALS },
{ ">", sdl.C.SDLK_GREATER },
{ "?", sdl.C.SDLK_QUESTION },
{ "@", sdl.C.SDLK_AT },
{ "[", sdl.C.SDLK_LEFTBRACKET },
{ "\\", sdl.C.SDLK_BACKSLASH },
{ "]", sdl.C.SDLK_RIGHTBRACKET },
{ "^", sdl.C.SDLK_CARET },
{ "_", sdl.C.SDLK_UNDERSCORE },
{ "`", sdl.C.SDLK_BACKQUOTE },
{ "a", sdl.C.SDLK_a },
{ "b", sdl.C.SDLK_b },
{ "c", sdl.C.SDLK_c },
{ "d", sdl.C.SDLK_d },
{ "e", sdl.C.SDLK_e },
{ "f", sdl.C.SDLK_f },
{ "g", sdl.C.SDLK_g },
{ "h", sdl.C.SDLK_h },
{ "i", sdl.C.SDLK_i },
{ "j", sdl.C.SDLK_j },
{ "k", sdl.C.SDLK_k },
{ "l", sdl.C.SDLK_l },
{ "m", sdl.C.SDLK_m },
{ "n", sdl.C.SDLK_n },
{ "o", sdl.C.SDLK_o },
{ "p", sdl.C.SDLK_p },
{ "q", sdl.C.SDLK_q },
{ "r", sdl.C.SDLK_r },
{ "s", sdl.C.SDLK_s },
{ "t", sdl.C.SDLK_t },
{ "u", sdl.C.SDLK_u },
{ "v", sdl.C.SDLK_v },
{ "w", sdl.C.SDLK_w },
{ "x", sdl.C.SDLK_x },
{ "y", sdl.C.SDLK_y },
{ "z", sdl.C.SDLK_z },
{ "capslock", sdl.C.SDLK_CAPSLOCK },
{ "f1", sdl.C.SDLK_F1 },
{ "f2", sdl.C.SDLK_F2 },
{ "f3", sdl.C.SDLK_F3 },
{ "f4", sdl.C.SDLK_F4 },
{ "f5", sdl.C.SDLK_F5 },
{ "f6", sdl.C.SDLK_F6 },
{ "f7", sdl.C.SDLK_F7 },
{ "f8", sdl.C.SDLK_F8 },
{ "f9", sdl.C.SDLK_F9 },
{ "f10", sdl.C.SDLK_F10 },
{ "f11", sdl.C.SDLK_F11 },
{ "f12", sdl.C.SDLK_F12 },
{ "printscreen", sdl.C.SDLK_PRINTSCREEN },
{ "scrolllock", sdl.C.SDLK_SCROLLLOCK },
{ "pause", sdl.C.SDLK_PAUSE },
{ "insert", sdl.C.SDLK_INSERT },
{ "home", sdl.C.SDLK_HOME },
{ "pageup", sdl.C.SDLK_PAGEUP },
{ "delete", sdl.C.SDLK_DELETE },
{ "end", sdl.C.SDLK_END },
{ "pagedown", sdl.C.SDLK_PAGEDOWN },
{ "right", sdl.C.SDLK_RIGHT },
{ "left", sdl.C.SDLK_LEFT },
{ "down", sdl.C.SDLK_DOWN },
{ "up", sdl.C.SDLK_UP },
{ "numlock", sdl.C.SDLK_NUMLOCKCLEAR },
{ "kp/", sdl.C.SDLK_KP_DIVIDE },
{ "kp*", sdl.C.SDLK_KP_MULTIPLY },
{ "kp-", sdl.C.SDLK_KP_MINUS },
{ "kp+", sdl.C.SDLK_KP_PLUS },
{ "kpenter", sdl.C.SDLK_KP_ENTER },
{ "kp0", sdl.C.SDLK_KP_0 },
{ "kp1", sdl.C.SDLK_KP_1 },
{ "kp2", sdl.C.SDLK_KP_2 },
{ "kp3", sdl.C.SDLK_KP_3 },
{ "kp4", sdl.C.SDLK_KP_4 },
{ "kp5", sdl.C.SDLK_KP_5 },
{ "kp6", sdl.C.SDLK_KP_6 },
{ "kp7", sdl.C.SDLK_KP_7 },
{ "kp8", sdl.C.SDLK_KP_8 },
{ "kp9", sdl.C.SDLK_KP_9 },
{ "kp.", sdl.C.SDLK_KP_PERIOD },
{ "kp,", sdl.C.SDLK_KP_COMMA },
{ "kp=", sdl.C.SDLK_KP_EQUALS },
{ "application", sdl.C.SDLK_APPLICATION },
{ "power", sdl.C.SDLK_POWER },
{ "f13", sdl.C.SDLK_F13 },
{ "f14", sdl.C.SDLK_F14 },
{ "f15", sdl.C.SDLK_F15 },
{ "f16", sdl.C.SDLK_F16 },
{ "f17", sdl.C.SDLK_F17 },
{ "f18", sdl.C.SDLK_F18 },
{ "f19", sdl.C.SDLK_F19 },
{ "f20", sdl.C.SDLK_F20 },
{ "f21", sdl.C.SDLK_F21 },
{ "f22", sdl.C.SDLK_F22 },
{ "f23", sdl.C.SDLK_F23 },
{ "f24", sdl.C.SDLK_F24 },
{ "execute", sdl.C.SDLK_EXECUTE },
{ "help", sdl.C.SDLK_HELP },
{ "menu", sdl.C.SDLK_MENU },
{ "select", sdl.C.SDLK_SELECT },
{ "stop", sdl.C.SDLK_STOP },
{ "again", sdl.C.SDLK_AGAIN },
{ "undo", sdl.C.SDLK_UNDO },
{ "cut", sdl.C.SDLK_CUT },
{ "copy", sdl.C.SDLK_COPY },
{ "paste", sdl.C.SDLK_PASTE },
{ "find", sdl.C.SDLK_FIND },
{ "mute", sdl.C.SDLK_MUTE },
{ "volumeup", sdl.C.SDLK_VOLUMEUP },
{ "volumedown", sdl.C.SDLK_VOLUMEDOWN },
{ "alterase", sdl.C.SDLK_ALTERASE },
{ "sysreq", sdl.C.SDLK_SYSREQ },
{ "cancel", sdl.C.SDLK_CANCEL },
{ "clear", sdl.C.SDLK_CLEAR },
{ "prior", sdl.C.SDLK_PRIOR },
{ "return2", sdl.C.SDLK_RETURN2 },
{ "separator", sdl.C.SDLK_SEPARATOR },
{ "out", sdl.C.SDLK_OUT },
{ "oper", sdl.C.SDLK_OPER },
{ "clearagain", sdl.C.SDLK_CLEARAGAIN },
{ "thsousandsseparator", sdl.C.SDLK_THOUSANDSSEPARATOR },
{ "decimalseparator", sdl.C.SDLK_DECIMALSEPARATOR },
{ "currencyunit", sdl.C.SDLK_CURRENCYUNIT },
{ "currencysubunit", sdl.C.SDLK_CURRENCYSUBUNIT },
{ "lctrl", sdl.C.SDLK_LCTRL },
{ "lshift", sdl.C.SDLK_LSHIFT },
{ "lalt", sdl.C.SDLK_LALT },
{ "lgui", sdl.C.SDLK_LGUI },
{ "rctrl", sdl.C.SDLK_RCTRL },
{ "rshift", sdl.C.SDLK_RSHIFT },
{ "ralt", sdl.C.SDLK_RALT },
{ "rgui", sdl.C.SDLK_RGUI },
{ "mode", sdl.C.SDLK_MODE },
{ "audionext", sdl.C.SDLK_AUDIONEXT },
{ "audioprev", sdl.C.SDLK_AUDIOPREV },
{ "audiostop", sdl.C.SDLK_AUDIOSTOP },
{ "audioplay", sdl.C.SDLK_AUDIOPLAY },
{ "audiomute", sdl.C.SDLK_AUDIOMUTE },
{ "mediaselect", sdl.C.SDLK_MEDIASELECT },
{ "www", sdl.C.SDLK_WWW },
{ "mail", sdl.C.SDLK_MAIL },
{ "calculator", sdl.C.SDLK_CALCULATOR },
{ "computer", sdl.C.SDLK_COMPUTER },
{ "appsearch", sdl.C.SDLK_AC_SEARCH },
{ "apphome", sdl.C.SDLK_AC_HOME },
{ "appback", sdl.C.SDLK_AC_BACK },
{ "appforward", sdl.C.SDLK_AC_FORWARD },
{ "appstop", sdl.C.SDLK_AC_STOP },
{ "apprefresh", sdl.C.SDLK_AC_REFRESH },
{ "appbookmarks", sdl.C.SDLK_AC_BOOKMARKS },
{ "brightnessdown", sdl.C.SDLK_BRIGHTNESSDOWN },
{ "brightnessup", sdl.C.SDLK_BRIGHTNESSUP },
{ "displayswitch", sdl.C.SDLK_DISPLAYSWITCH },
{ "kbdillumtoggle", sdl.C.SDLK_KBDILLUMTOGGLE },
{ "kbdillumdown", sdl.C.SDLK_KBDILLUMDOWN },
{ "kbdillumup", sdl.C.SDLK_KBDILLUMUP },
{ "eject", sdl.C.SDLK_EJECT },
{ "sleep", sdl.C.SDLK_SLEEP },
}
return Keyboard

View File

@ -1,66 +0,0 @@
local REL = (...):gsub('[^.]*$', '')
local ffi = require 'ffi'
local sdl = require(REL .. 'sdl2.init')
sdl.AudioCVT = ffi.typeof 'SDL_AudioCVT'
-- sdl.AudioDeviceEvent = ffi.typeof 'SDL_AudioDeviceEvent'
sdl.AudioSpec = ffi.typeof 'SDL_AudioSpec'
sdl.Color = ffi.typeof 'SDL_Color'
sdl.ControllerAxisEvent = ffi.typeof 'SDL_ControllerAxisEvent'
sdl.ControllerButtonEvent = ffi.typeof 'SDL_ControllerButtonEvent'
sdl.ControllerDeviceEvent = ffi.typeof 'SDL_ControllerDeviceEvent'
sdl.DisplayMode = ffi.typeof 'SDL_DisplayMode'
sdl.DollarGestureEvent = ffi.typeof 'SDL_DollarGestureEvent'
sdl.DropEvent = ffi.typeof 'SDL_DropEvent'
sdl.Event = ffi.typeof 'SDL_Event'
sdl.Finger = ffi.typeof 'SDL_Finger'
sdl.HapticCondition = ffi.typeof 'SDL_HapticCondition'
sdl.HapticConstant = ffi.typeof 'SDL_HapticConstant'
sdl.HapticCustom = ffi.typeof 'SDL_HapticCustom'
sdl.HapticDirection = ffi.typeof 'SDL_HapticDirection'
sdl.HapticEffect = ffi.typeof 'SDL_HapticEffect'
sdl.HapticLeftRight = ffi.typeof 'SDL_HapticLeftRight'
sdl.HapticPeriodic = ffi.typeof 'SDL_HapticPeriodic'
sdl.HapticRamp = ffi.typeof 'SDL_HapticRamp'
sdl.JoyAxisEvent = ffi.typeof 'SDL_JoyAxisEvent'
sdl.JoyBallEvent = ffi.typeof 'SDL_JoyBallEvent'
sdl.JoyButtonEvent = ffi.typeof 'SDL_JoyButtonEvent'
sdl.JoyDeviceEvent = ffi.typeof 'SDL_JoyDeviceEvent'
sdl.JoyHatEvent = ffi.typeof 'SDL_JoyHatEvent'
sdl.KeyboardEvent = ffi.typeof 'SDL_KeyboardEvent'
sdl.Keysym = ffi.typeof 'SDL_Keysym'
sdl.MessageBoxButtonData = ffi.typeof 'SDL_MessageBoxButtonData'
sdl.MessageBoxColor = ffi.typeof 'SDL_MessageBoxColor'
sdl.MessageBoxColorScheme = ffi.typeof 'SDL_MessageBoxColorScheme'
sdl.MessageBoxData = ffi.typeof 'SDL_MessageBoxData'
sdl.MouseButtonEvent = ffi.typeof 'SDL_MouseButtonEvent'
sdl.MouseMotionEvent = ffi.typeof 'SDL_MouseMotionEvent'
sdl.MouseWheelEvent = ffi.typeof 'SDL_MouseWheelEvent'
sdl.MultiGestureEvent = ffi.typeof 'SDL_MultiGestureEvent'
sdl.Palette = ffi.typeof 'SDL_Palette'
sdl.PixelFormat = ffi.typeof 'SDL_PixelFormat'
sdl.Point = ffi.typeof 'SDL_Point'
sdl.QuitEvent = ffi.typeof 'SDL_QuitEvent'
sdl.RWops = ffi.typeof 'SDL_RWops'
sdl.Rect = ffi.typeof 'SDL_Rect'
sdl.RendererInfo = ffi.typeof 'SDL_RendererInfo'
sdl.Surface = ffi.typeof 'SDL_Surface'
sdl.SysWMEvent = ffi.typeof 'SDL_SysWMEvent'
-- sdl.SysWMinfo = ffi.typeof 'SDL_SysWMinfo'
sdl.SysWMmsg = ffi.typeof 'SDL_SysWMmsg'
sdl.TextEditingEvent = ffi.typeof 'SDL_TextEditingEvent'
sdl.TextInputEvent = ffi.typeof 'SDL_TextInputEvent'
sdl.Texture = ffi.typeof 'SDL_Texture'
sdl.TouchFingerEvent = ffi.typeof 'SDL_TouchFingerEvent'
sdl.UserEvent = ffi.typeof 'SDL_UserEvent'
sdl.WindowEvent = ffi.typeof 'SDL_WindowEvent'
sdl.assert_data = ffi.typeof 'SDL_assert_data'
sdl.atomic_t = ffi.typeof 'SDL_atomic_t'
sdl.version = ffi.typeof 'SDL_version'
if sdl.init(sdl.INIT_VIDEO) ~= 0 then
error(ffi.string(sdl.getError()))
end
return sdl

View File

@ -1,36 +0,0 @@
Copyright (c) 2011-2014 Idiap Research Institute (Ronan Collobert)
Copyright (c) 2012-2014 Deepmind Technologies (Koray Kavukcuoglu)
Copyright (c) 2011-2012 NEC Laboratories America (Koray Kavukcuoglu)
Copyright (c) 2011-2013 NYU (Clement Farabet)
Copyright (c) 2006-2010 NEC Laboratories America (Ronan Collobert, Leon Bottou, Iain Melvin, Jason Weston)
Copyright (c) 2006 Idiap Research Institute (Samy Bengio)
Copyright (c) 2001-2004 Idiap Research Institute (Ronan Collobert, Samy Bengio, Johnny Mariethoz)
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the names of Deepmind Technologies, NYU, NEC Laboratories America
and IDIAP Research Institute nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,32 +0,0 @@
sdl2-ffi
========
A LuaJIT interface to SDL2
# Installation #
First, make sure SDL2 is installed on your system. This package only requires the binary shared libraries (.so, .dylib, .dll).
Please see your package management system to install SDL2. You can also download yourself binaries on the
[SDL2 web page](http://libsdl.org/download-2.0.php)
```sh
luarocks install https://raw.github.com/torch/sdl2-ffi/master/rocks/sdl2-scm-1.rockspec
```
*Note*: this SDL interface supports only SDL2, not SDL 1.2.
# Usage #
```lua
local sdl = require 'sdl2'
sdl.init(sdl.INIT_VIDEO)
...
```
All SDL C functions are available in the `sdl` namespace returned by require. The only difference is the naming, which is not prefixed
by `SDL_` anymore. The same goes for all C defines (like `SDL_INIT_VIDEO`, which can now be accessed with `sdl.INIT_VIDEO`).
Although the interface is quite complete, there are still few defines not ported in this package. Fill free to post a message about it,
or to request pulls.

File diff suppressed because it is too large Load Diff

View File

@ -1,58 +0,0 @@
-- Function definitions which were not output by
-- the C preprocessor
local sdl
local function registerdefines(sdl)
-- audio
function sdl.AUDIO_BITSIZE(x)
return bit.band(x, sdl.AUDIO_MASK_BITSIZE)
end
function sdl.AUDIO_ISFLOAT(x)
return bit.band(x, sdl.AUDIO_MASK_DATATYPE) ~= 0
end
function sdl.AUDIO_ISBIGENDIAN(x)
return bit.band(x, sdl.AUDIO_MASK_ENDIAN) ~= 0
end
function sdl.AUDIO_ISSIGNED(x)
return bit.band(x, sdl.AUDIO_MASK_SIGNED) ~= 0
end
function sdl.AUDIO_ISINT(x)
return not sdl.AUDIO_ISFLOAT(x)
end
function sdl.AUDIO_ISLITTLEENDIAN(x)
return not sdl.AUDIO_ISBIGENDIAN(x)
end
function sdl.AUDIO_ISUNSIGNED(x)
return not sdl.AUDIO_ISSIGNED(x)
end
function sdl.loadWAV(file, spec, audio_buf, audio_len)
return sdl.loadWAV_RW(sdl.RWFromFile(file, "rb"), 1, spec, audio_buf, audio_len)
end
-- surface
sdl.blitSurface = sdl.upperBlit
function sdl.MUSTLOCK(S)
return bit.band(S.flags, sdl.RLEACCEL)
end
function sdl.loadBMP(file)
return sdl.loadBMP_RW(sdl.RWFromFile(file, 'rb'), 1)
end
function sdl.saveBMP(surface, file)
return sdl.saveBMP_RW(surface, sdl.RWFromFile(file, 'wb'), 1)
end
end
return registerdefines

File diff suppressed because it is too large Load Diff

View File

@ -1,79 +0,0 @@
local REL = (...):gsub('[^.]*$', '')
local sdl = require(REL .. 'sdl')
local SpriteBatch = setmetatable({}, { __call = function (self, ...)
local object = setmetatable({}, { __index = self })
return object, self.constructor(object, ...)
end })
--[[
spriteBatch = SpriteBatch( image, size )
Arguments
Image image
The Image to use for the sprites.
number size (1000)
The max number of sprites.
Returns
SpriteBatch spriteBatch
The new SpriteBatch.
--]]
function SpriteBatch:constructor (image)
self.image = image
self.sprites = {}
end
function SpriteBatch:clear ()
self.sprites = {}
end
--[[
id = SpriteBatch:add( quad, x, y, r, sx, sy )
Arguments
Quad quad
The Quad to add.
number x
The position to draw the object (x-axis).
number y
The position to draw the object (y-axis).
number r (0)
Orientation (radians). (not implemented)
number sx (1)
Scale factor (x-axis).
number sy (sx)
Scale factor (y-axis).
Returns
number id
An identifier for the added sprite.
--]]
function SpriteBatch:add (quad, x, y, r, sx, sy)
local sprites = self.sprites
sprites[#sprites + 1] = { quad = quad, x = x, y = y,
sx = sx or 1, sy = sy or 1 }
end
function SpriteBatch:draw ()
local image = self.image
local renderer = image.sdlRenderer
local texture = image.sdlTexture
for _, sprite in ipairs(self.sprites) do
local quad = sprite.quad
local w = math.ceil(quad[3] * sprite.sx)
local h = math.ceil(quad[4] * sprite.sy)
local src = sdl.Rect(quad)
local dst = sdl.Rect(sprite.x, sprite.y, w, h)
sdl.renderCopy(renderer, texture, src, dst)
end
end
return SpriteBatch

View File

@ -1,104 +0,0 @@
local ROOT = (...):gsub('[^.]*.[^.]*.[^.]*$', '')
local REL = (...):gsub('[^.]*$', '')
local ffi = require 'ffi'
local sdl = require(REL .. 'sdl')
local Font = require(REL .. 'font')
local ttf = Font.SDL2_ttf
local Multiline = require(ROOT .. 'multiline')
local Text = setmetatable({}, { __call = function (self, ...)
local object = setmetatable({}, { __index = self })
return object, self.constructor(object, ...)
end })
local function renderSingle (self, font, text, color)
local alphaMod = color and color[4]
color = sdl.Color(color or 0)
local surface = ffi.gc(
ttf.TTF_RenderUTF8_Blended(font.sdlFont, text, color),
sdl.freeSurface)
self.sdlSurface = surface
self.sdlTexture = ffi.gc(
sdl.createTextureFromSurface(self.sdlRenderer, surface),
sdl.destroyTexture)
if alphaMod then
sdl.setTextureAlphaMod(self.sdlTexture, alphaMod)
end
self.width, self.height = surface.w, surface.h
end
local function renderMulti (self, font, text, color, align, limit)
local alphaMod = color and color[4]
local lines = Multiline.wrap(font, text, limit)
local lineHeight = font:getLineHeight()
local height = #lines * lineHeight
color = sdl.Color(color or 0)
-- mask values from SDL_ttf.c
-- TODO: something with sdl.BYTEORDER == sdl.BIG_ENDIAN ?
local r, g, b, a = 0x00FF0000, 0x0000FF00, 0x000000FF, 0xFF000000
local surface = ffi.gc(
sdl.createRGBSurface(sdl.SWSURFACE, limit, height, 32, r, g, b, a),
sdl.freeSurface)
self.sdlSurface = surface
for index, line in ipairs(lines) do
local text = table.concat(line)
local lineSurface = ffi.gc(
ttf.TTF_RenderUTF8_Blended(font.sdlFont, text, color),
sdl.freeSurface)
if lineSurface ~= nil then
sdl.setSurfaceBlendMode(lineSurface, sdl.BLENDMODE_NONE)
local w, h = lineSurface.w, lineSurface.h
local top = (index - 1) * lineHeight
if align == 'left' then
sdl.blitSurface(lineSurface, nil, surface,
sdl.Rect(0, top, w, h))
elseif align == 'right' then
sdl.blitSurface(lineSurface, nil, surface,
sdl.Rect(limit - line.width, top, w, h))
elseif align == 'center' then
sdl.blitSurface(lineSurface, nil, surface,
sdl.Rect((limit - line.width) / 2, top, w, h))
end
end
end
self.sdlTexture = ffi.gc(
sdl.createTextureFromSurface(self.sdlRenderer, surface),
sdl.destroyTexture)
if alphaMod then
sdl.setTextureAlphaMod(self.sdlTexture, alphaMod)
end
self.width, self.height = limit, height
end
function Text:constructor (renderer, font, text, color, align, limit)
self.width, self.height = 0, 0
if not text or text == '' then return end
self.sdlRenderer = renderer
if limit then
renderMulti(self, font, text, color, align, limit)
else
renderSingle(self, font, text, color)
end
end
function Text:getWidth ()
return self.width
end
function Text:getHeight ()
return self.height
end
return Text

View File

@ -1,177 +0,0 @@
local ROOT = (...):gsub('[^.]*.[^.]*$', '')
local Base = require(ROOT .. 'base')
local Hooker = require(ROOT .. 'hooker')
local Backend = {}
Backend.isMac = function ()
return love.system.getOS() == 'OS X'
end
Backend.run = function () end
Backend.Cursor = love.mouse.newCursor
Backend.Font = require(ROOT .. 'backend.love.font')
Backend.Text = require(ROOT .. 'backend.love.text')
Backend.Image = love.graphics.newImage
Backend.Quad = love.graphics.newQuad
Backend.SpriteBatch = love.graphics.newSpriteBatch
-- love.graphics.draw( drawable, x, y, r, sx, sy, ox, oy, kx, ky )
Backend.draw = function (drawable, ...)
if drawable.typeOf and drawable:typeOf 'Drawable' then
return love.graphics.draw(drawable, ...)
end
return drawable:draw(...)
end
Backend.drawRectangle = love.graphics.rectangle
Backend.print = love.graphics.print
Backend.getClipboardText = love.system.getClipboardText
Backend.setClipboardText = love.system.setClipboardText
Backend.getMousePosition = love.mouse.getPosition
Backend.setMousePosition = love.mouse.setPosition
Backend.getSystemCursor = love.mouse.getSystemCursor
Backend.getWindowSize = function ()
return love.graphics.getWidth(), love.graphics.getHeight()
end
Backend.getTime = love.timer.getTime
Backend.isKeyDown = love.keyboard.isDown
Backend.isMouseDown = love.mouse.isDown
Backend.pop = love.graphics.pop
local push = love.graphics.push
Backend.push = function ()
return push 'all'
end
Backend.quit = function ()
love.event.quit()
end
if _G.love._version_major >= 11 then
Backend.setColor = function(r, g, b, a)
if type(r) == "table" then
r, g, b, a = r[1], r[2], r[3], r[4]
end
if a == nil then
a = 255
end
love.graphics.setColor(r / 255, g / 255, b / 255, a / 255)
end
else
Backend.setColor = love.graphics.setColor
end
Backend.setCursor = love.mouse.setCursor
Backend.setFont = function (font)
return love.graphics.setFont(font.loveFont)
end
Backend.setScissor = love.graphics.setScissor
Backend.getScissor = love.graphics.getScissor
Backend.intersectScissor = love.graphics.intersectScissor
function Backend.hide (layout)
for _, item in ipairs(layout.hooks) do
Hooker.unhook(item)
end
layout.hooks = {}
end
local function hook (layout, key, method, hookLast)
layout.hooks[#layout.hooks + 1] = Hooker.hook(love, key, method, hookLast)
end
local getMouseButtonId, isMouseDown
if love._version_major == 0 and love._version_minor < 10 then
getMouseButtonId = function (value)
return value == 'l' and 'left'
or value == 'r' and 'right'
or value == 'm' and 'middle'
or value == 'x1' and 4
or value == 'x2' and 5
or value
end
isMouseDown = function ()
return love.mouse.isDown('l', 'r', 'm')
end
else
getMouseButtonId = function (value)
return value == 1 and 'left'
or value == 2 and 'right'
or value == 3 and 'middle'
or value
end
isMouseDown = function ()
return love.mouse.isDown(1, 2, 3)
end
end
function Backend.show (layout)
local input = layout.input
hook(layout, 'draw', function ()
input:handleDisplay(layout)
end, true)
hook(layout, 'resize', function (width, height)
return input:handleReshape(layout, width, height)
end)
hook(layout, 'mousepressed', function (x, y, button)
if button == 'wu' or button == 'wd' then
return input:handleWheelMove(layout, 0, button == 'wu' and 1 or -1)
end
return input:handlePressStart(layout, getMouseButtonId(button), x, y)
end)
hook(layout, 'mousereleased', function (x, y, button)
return input:handlePressEnd(layout, getMouseButtonId(button), x, y)
end)
hook(layout, 'mousemoved', function (x, y, dx, dy)
if isMouseDown() then
return input:handlePressedMove(layout, x, y)
else
return input:handleMove(layout, x, y)
end
end)
hook(layout, 'keypressed', function (key, sc, isRepeat)
if key == ' ' then key = 'space' end
return input:handleKeyPress(layout, key, sc, Backend.getMousePosition())
end)
hook(layout, 'keyreleased', function (key, sc)
if key == ' ' then key = 'space' end
return input:handleKeyRelease(layout, key, sc, Backend.getMousePosition())
end)
hook(layout, 'textinput', function (text)
return input:handleTextInput(layout, text, Backend.getMousePosition())
end)
if (love._version_major == 0 and love._version_minor > 9) or love._version_major >= 11 then
hook(layout, 'wheelmoved', function (x, y)
return input:handleWheelMove(layout, x, y)
end)
end
end
return Backend

View File

@ -1,53 +0,0 @@
local Font = setmetatable({}, { __call = function (self, ...)
local object = setmetatable({}, { __index = self })
return object, self.constructor(object, ...)
end })
local fontCache = {}
function Font:constructor (path, size, color)
if not size then
size = 12
end
if not color then
color = { 0, 0, 0 }
end
local key = (path or '') .. '_' .. size
if not fontCache[key] then
if path then
fontCache[key] = love.graphics.newFont(path, size)
else
fontCache[key] = love.graphics.newFont(size)
end
end
self.loveFont = fontCache[key]
self.color = color
end
function Font:setAlignment (align)
self.align = align
end
function Font:setWidth (width)
self.width = width
end
function Font:getLineHeight ()
return self.loveFont:getHeight()
end
function Font:getAscender ()
return self.loveFont:getAscent()
end
function Font:getDescender ()
return self.loveFont:getDescent()
end
function Font:getAdvance (text)
return (self.loveFont:getWidth(text))
end
return Font

View File

@ -1,75 +0,0 @@
local ROOT = (...):gsub('[^.]*.[^.]*.[^.]*$', '')
local REL = (...):gsub('[^.]*$', '')
local Multiline = require(ROOT .. 'multiline')
local Text = setmetatable({}, { __call = function (self, ...)
local object = setmetatable({}, { __index = self })
return object, self.constructor(object, ...)
end })
local function renderSingle (self, x, y, font, text, color)
love.graphics.push('all')
love.graphics.setColor(color or { 0, 0, 0 })
love.graphics.setFont(font.loveFont)
love.graphics.print(text, math.floor(x), math.floor(y))
love.graphics.pop()
self.height = font:getLineHeight()
self.width = font:getAdvance(text)
end
local function renderMulti (self, x, y, font, text, color, align, limit)
local lines = Multiline.wrap(font, text, limit)
local lineHeight = font:getLineHeight()
local height = #lines * lineHeight
love.graphics.push('all')
love.graphics.setColor(color or { 0, 0, 0 })
love.graphics.setFont(font.loveFont)
for index, line in ipairs(lines) do
local text = table.concat(line)
local top = (index - 1) * lineHeight
local w = line.width
if align == 'left' then
love.graphics.print(text,
math.floor(x), math.floor(top + y))
elseif align == 'right' then
love.graphics.print(text,
math.floor(limit - w + x), math.floor(top + y))
elseif align == 'center' then
love.graphics.print(text,
math.floor((limit - w) / 2 + x), math.floor(top + y))
end
end
love.graphics.pop()
self.height = height
self.width = limit
end
function Text:constructor (font, text, color, align, limit)
if limit then
function self:draw (x, y)
return renderMulti(self, x, y, font, text, color, align, limit)
end
else
function self:draw (x, y)
return renderSingle(self, x, y, font, text, color)
end
end
self:draw(-1000000, -1000000)
end
function Text:getWidth ()
return self.width
end
function Text:getHeight ()
return self.height
end
return Text

View File

@ -1,13 +0,0 @@
return {
extend = function (self, subtype)
return setmetatable(subtype or {}, {
__index = self,
__call = function (self, ...)
local instance = setmetatable({}, { __index = self })
return instance, instance:constructor(...)
end
})
end,
constructor = function () end,
}

View File

@ -1,251 +0,0 @@
local RESOURCE = (...):gsub('%.', '/') .. '/'
return function (config)
config = config or {}
local resources = assert(config.resources, 'missing config.resources')
local backColor = config.backColor or { 240, 240, 240 }
local lineColor = config.lineColor or { 220, 220, 220 }
local textColor = config.textColor or { 0, 0, 0 }
local highlight = config.highlight or { 0x19, 0xAE, 0xFF }
local button_pressed = resources .. 'button_pressed.png'
local button_focused = resources .. 'button_focused.png'
local button_hovered = resources .. 'button_hovered.png'
local button = resources .. 'button.png'
local check_checked_pressed = resources .. 'check_checked_pressed.png'
local check_unchecked_pressed = resources .. 'check_unchecked_pressed.png'
local check_checked_focused = resources .. 'check_checked_focused.png'
local check_unchecked_focused = resources .. 'check_unchecked_focused.png'
local check_checked = resources .. 'check_checked.png'
local check_unchecked = resources .. 'check_unchecked.png'
local radio_checked_pressed = resources .. 'radio_checked_pressed.png'
local radio_unchecked_pressed = resources .. 'radio_unchecked_pressed.png'
local radio_checked_focused = resources .. 'radio_checked_focused.png'
local radio_unchecked_focused = resources .. 'radio_unchecked_focused.png'
local radio_checked = resources .. 'radio_checked.png'
local radio_unchecked = resources .. 'radio_unchecked.png'
local triangle_left = resources .. 'triangle_left.png'
local triangle_up = resources .. 'triangle_up.png'
local triangle_right = resources .. 'triangle_right.png'
local triangle_down = resources .. 'triangle_down.png'
local text_focused = resources .. 'text_focused.png'
local text = resources .. 'text.png'
local function getButtonSlices (self)
return self.pressed.left and button_pressed
or self.focused and button_focused
or self.hovered and button_hovered
or button
end
local function getCheckIcon (self)
if self.pressed.left then
return self.value and check_checked_pressed
or check_unchecked_pressed
end
if self.focused then
return self.value and check_checked_focused
or check_unchecked_focused
end
return self.value and check_checked or check_unchecked
end
local function getControlHeight (self)
return self.flow == 'x' and self._defaultDimension
end
local function getControlWidth (self)
return self.flow ~= 'x' and self._defaultDimension
end
local function getMenuItemBackground (self)
return self.active and highlight
end
local function getRadioIcon (self)
if self.pressed.left then
return self.value and radio_checked_pressed
or radio_unchecked_pressed
end
if self.focused then
return self.value and radio_checked_focused
or radio_unchecked_focused
end
return self.value and radio_checked or radio_unchecked
end
local function getSashBackground (self)
return self.hovered and highlight or lineColor
end
local function getSashHeight (self)
return self.parent and self.parent.flow ~= 'x' and 4
end
local function getSashWidth (self)
return self.parent and self.parent.flow == 'x' and 4
end
local function getSliderThumbWidth (self)
return self.parent.flow == 'x' and 32 or false
end
local function getSliderThumbHeight (self)
return self.parent.flow ~= 'x' and 32 or false
end
local function getStepperBeforeIcon (self)
return self.parent.flow == 'x' and triangle_left or triangle_up
end
local function getStepperAfterIcon (self)
return self.parent.flow == 'x' and triangle_right or triangle_down
end
local function getTextSlices (self)
return self.focused and text_focused or text
end
return {
-- generic types for widgets to inherit
Control = {
flow = 'x',
height = getControlHeight,
width = getControlWidth,
color = textColor,
align = 'center middle',
margin = 2,
color = textColor,
solid = true,
_defaultDimension = 36,
},
Line = {
margin = 0,
padding = 4,
align = 'left middle',
_defaultDimension = 24,
},
-- widget types
button = {
type = { 'Control' },
padding = 6,
slices = getButtonSlices,
focusable = true,
},
check = {
type = { 'Line', 'Control' },
focusable = true,
icon = getCheckIcon,
},
label = {
type = { 'Line', 'Control' },
},
menu = {
flow = 'x',
height = 24,
background = backColor,
color = textColor,
},
['menu.expander'] = {
icon = resources .. 'triangle_right.png',
},
['menu.item'] = {
padding = 4,
height = 24,
align = 'left middle',
background = getMenuItemBackground,
},
panel = {
padding = 2,
background = backColor,
color = textColor,
solid = true,
},
progress = {
type = { 'Control' },
slices = resources .. 'button_pressed.png',
},
['progress.bar'] = {
slices = resources .. 'progress.png',
minwidth = 12,
minheight = 22,
},
radio = {
type = { 'Line', 'Control' },
focusable = true,
icon = getRadioIcon,
},
sash = {
background = getSashBackground,
height = getSashHeight,
width = getSashWidth,
},
slider = {
type = { 'Control' },
slices = resources .. 'button_pressed.png',
},
['slider.thumb'] = {
type = { 'button' },
align = 'middle center',
margin = 0,
width = getSliderThumbWidth,
height = getSliderThumbHeight,
},
status = {
type = { 'Line', 'Control' },
background = backColor,
color = textColor,
},
stepper = {
type = { 'Control' },
slices = resources .. 'button_pressed.png',
},
['stepper.after'] = {
type = { 'button' },
icon = getStepperAfterIcon,
margin = 0,
minwidth = 32,
minheight = 32,
},
['stepper.before'] = {
type = { 'button' },
icon = getStepperBeforeIcon,
margin = 0,
minwidth = 32,
minheight = 32,
},
['stepper.item'] = {
align = 'center middle',
color = textColor,
},
['stepper.view'] = {
margin = 4,
},
submenu = {
padding = 10,
margin = -10,
slices = resources .. 'submenu.png',
color = textColor,
solid = true,
},
text = {
type = { 'Control' },
align = 'left middle',
slices = getTextSlices,
padding = 6,
focusable = true,
cursor = 'ibeam',
highlight = highlight,
},
}
end

View File

@ -1,69 +0,0 @@
--[[--
Event class.
@classmod Event
--]]--
local ROOT = (...):gsub('[^.]*$', '')
local Base = require(ROOT .. 'base')
local Hooker = require(ROOT .. 'hooker')
local Event = Base:extend({ name = 'Event' })
function Event:emit (target, data, defaultAction)
local callbacks = self.registry[target]
local result = callbacks and callbacks(data or {})
if result ~= nil then return result end
if defaultAction then defaultAction() end
end
function Event:bind (target, callback)
local registry = self.registry
return Hooker.hook(registry, target, callback)
end
--[[--
Event names.
--]]--
Event.names = {
'Reshape', -- A widget is being reshaped.
'PreDisplay', -- A widget is about to be drawn.
'Display', -- A widget was drawn.
'KeyPress', -- A keyboard key was pressed.
'KeyRelease', -- A keyboard key was released.
'TextInput', -- Text was entered.
'Move', -- The cursor moved, and no button was pressed.
'Focus', -- A widget received focus.
'Blur', -- A widget lost focus.
'Enter', -- The cursor entered a widget, and no button was pressed.
'Leave', -- The cursor left a widget, and no button was pressed.
'PressEnter', -- The cursor entered a widget, and a button was pressed.
'PressLeave', -- The cursor left a widget, and a button was pressed.
'PressStart', -- A pointer button or keyboard shortcut was pressed.
'PressEnd', -- A pointer button or keyboard shortcut was released.
'PressDrag', -- A pressed cursor moved; targets originating widget.
'PressMove', -- A pressed cursor moved; targets widget at cursor position.
'Press', -- A pointer button was pressed and released on the same widget.
'Change', -- A widget's value changed.
'WheelMove', -- The scroll wheel on the mouse moved.
'Show', -- A layout is shown.
'Hide', -- A layout is hidden.
}
local weakKeyMeta = { __mode = 'k' }
for i, name in ipairs(Event.names) do
Event[name] = Event:extend({
name = name,
registry = setmetatable({}, weakKeyMeta),
})
end
function Event.injectBinders (t)
for i, name in ipairs(Event.names) do
t['on' .. name] = function (...) return Event[name]:bind(...) end
end
end
return Event

View File

@ -1,91 +0,0 @@
local Hooker = {}
local wrapped = setmetatable({}, { __mode = 'k' })
local hooks = setmetatable({}, { __mode = 'k' })
local function unhook (item)
if item.prev then
item.prev.next = item.next
end
if item.next then
item.next.prev = item.prev
end
if hooks[item.host][item.key] == item then
hooks[item.host][item.key] = item.next
end
end
local function hook (host, key, func, atEnd)
if not func then
return
end
if not hooks[host] then
hooks[host] = {}
end
local current = hooks[host][key]
local item = {
next = not atEnd and current or nil,
unhook = unhook,
host = host,
key = key,
func = func,
}
if atEnd then
if current then
while current.next do
current = current.next
end
current.next = item
item.prev = current
else
hooks[host][key] = item
end
return item
end
if current then
current.prev = item
end
hooks[host][key] = item
return item
end
function Hooker.unhook (item)
return unhook(item)
end
function Hooker.hook (host, key, func, atEnd)
if not wrapped[host] then
wrapped[host] = {}
end
if not wrapped[host][key] then
wrapped[host][key] = true
hook(host, key, host[key])
host[key] = function (...)
local item = hooks[host][key]
while item do
local result = item.func(...)
if result ~= nil then
return result
end
item = item.next
end -- while
end -- function
end -- if
return hook(host, key, func, atEnd)
end
return Hooker

View File

@ -1,232 +0,0 @@
local ROOT = (...):gsub('[^.]*$', '')
local Backend = require(ROOT .. 'backend')
local Base = require(ROOT .. 'base')
local Event = require(ROOT .. 'event')
local Shortcut = require(ROOT .. 'shortcut')
local Input = Base:extend()
local weakValueMeta = { __mode = 'v' }
function Input:constructor ()
self.pressedWidgets = setmetatable({}, weakValueMeta)
self.passedWidgets = setmetatable({}, weakValueMeta)
end
function Input:handleDisplay (layout)
local root = layout.root
if root then root:paint() end
Event.Display:emit(layout)
end
function Input:handleKeyPress (layout, key, sc, x, y)
local widget = layout.focusedWidget or layout.root
local result = widget:bubbleEvent('KeyPress', {
key = key,
scanCode = sc,
modifierFlags = Shortcut.getModifierFlags(),
x = x, y = y
})
if result ~= nil then return result end
if layout.root.modal then return false end
end
function Input:handleKeyRelease (layout, key, sc, x, y)
local widget = layout.focusedWidget or layout.root
local result = widget:bubbleEvent('KeyRelease', {
key = key,
scanCode = sc,
modifierFlags = Shortcut.getModifierFlags(),
x = x, y = y
})
if result ~= nil then return result end
if layout.root.modal then return false end
end
function Input:handleTextInput (layout, text, x, y)
local widget = layout.focusedWidget or layout.root
local result = widget:bubbleEvent('TextInput', {
text = text,
x = x, y = y
})
if result ~= nil then return result end
if layout.root.modal then return false end
end
local function checkHit (widget, layout)
local root = layout.root
return widget and widget.solid or root.modal, widget or root
end
function Input:handleMove (layout, x, y)
local hit, widget = checkHit(layout:getWidgetAt(x, y), layout)
local previousWidget = self.previousMoveWidget
if widget ~= previousWidget then
if previousWidget then
for ancestor in previousWidget:eachAncestor(true) do
ancestor.hovered = nil
end
end
for ancestor in widget:eachAncestor(true) do
ancestor.hovered = true
end
end
widget:bubbleEvent('Move', {
hit = hit,
oldTarget = previousWidget,
x = x, y = y
})
if widget ~= previousWidget then
if previousWidget then
previousWidget:bubbleEvent('Leave', {
hit = hit,
newTarget = widget,
x = x, y = y
})
end
widget:bubbleEvent('Enter', {
hit = hit,
oldTarget = previousWidget,
x = x, y = y
})
if widget.cursor then
Backend.setCursor(Backend.getSystemCursor(widget.cursor))
else
Backend.setCursor()
end
self.previousMoveWidget = widget
end
return hit
end
function Input:handlePressedMove (layout, x, y)
local hit, widget = checkHit(layout:getWidgetAt(x, y), layout)
for _, button in ipairs { 'left', 'middle', 'right' } do
local originWidget = self.pressedWidgets[button]
if originWidget then
local passedWidget = self.passedWidgets[button]
originWidget:bubbleEvent('PressDrag', {
hit = hit,
newTarget = widget,
button = button,
x = x, y = y
})
if (widget == passedWidget) then
widget:bubbleEvent('PressMove', {
hit = hit,
origin = originWidget,
button = button,
x = x, y = y
})
else
originWidget.pressed[button] = (widget == originWidget) or nil
if passedWidget then
passedWidget:bubbleEvent('PressLeave', {
hit = hit,
newTarget = widget,
origin = originWidget,
button = button,
x = x, y = y
})
end
widget:bubbleEvent('PressEnter', {
hit = hit,
oldTarget = passedWidget,
origin = originWidget,
button = button,
x = x, y = y
})
self.passedWidgets[button] = widget
end
end -- if originWidget
end -- mouse buttons
return hit
end
function Input:handlePressStart (layout, button, x, y, widget, shortcut)
local hit, widget = checkHit(widget or layout:getWidgetAt(x, y), layout)
-- if hit then
self.pressedWidgets[button] = widget
self.passedWidgets[button] = widget
widget.pressed[button] = true
if button == 'left' then
widget:focus()
end
-- end
widget:bubbleEvent('PressStart', {
hit = hit,
button = button,
shortcut = shortcut,
x = x, y = y
})
return hit
end
function Input:handlePressEnd (layout, button, x, y, widget, shortcut)
local originWidget = widget or self.pressedWidgets[button]
if not originWidget then return end
local hit, widget = checkHit(widget or layout:getWidgetAt(x, y), layout)
local wasPressed = originWidget.pressed[button]
if hit then
originWidget.pressed[button] = nil
end
widget:bubbleEvent('PressEnd', {
hit = hit,
origin = originWidget,
shortcut = shortcut,
button = button,
x = x, y = y
})
if (widget == originWidget and wasPressed) then
widget:bubbleEvent('Press', {
hit = hit,
button = button,
shortcut = shortcut,
x = x, y = y
})
end
if hit then
self.pressedWidgets[button] = nil
self.passedWidgets[button] = nil
end
return hit
end
function Input:handleReshape (layout, width, height)
local root = layout.root
root:reshape()
if root.type ~= 'window' then -- FIXME: move stuff below to a Widget method
if not root.width then
root.dimensions.width = width
end
if not root.height then
root.dimensions.height = height
end
end
Event.Reshape:emit(layout, {
target = layout,
width = width,
height = height
})
end
function Input:handleWheelMove (layout, scrollX, scrollY)
local x, y = Backend.getMousePosition()
local hit, widget = checkHit(layout:getWidgetAt(x, y), layout)
widget:bubbleEvent('WheelMove', {
hit = hit,
x = x, y = y,
scrollX = scrollX, scrollY = scrollY
})
return hit
end
Input.default = Input()
return Input

View File

@ -1,54 +0,0 @@
--[[--
Launcher for the LuaJIT backend.
Looks for main.lua. Launch it like this:
luajit myapp/lib/luigi/launch.lua
If luigi isn't inside the project directory, pass
the path containing main.lua as the second argument.
The path must end with a directory separator.
luajit /opt/luigi/launch.lua ./myapp/
If the app prefixes luigi modules with something
other then 'luigi', pass that prefix as the third
argument.
luajit /opt/luigi/launch.lua ./myapp/ lib.luigi
--]]--
local packagePath = package.path
local libRoot = arg[0]:gsub('[^/\\]*%.lua$', '')
local appRoot, modPath = ...
local function run (appRoot, modPath)
package.path = packagePath .. ';' .. appRoot .. '?.lua'
rawset(_G, 'LUIGI_APP_ROOT', appRoot)
require 'main'
require (modPath .. '.backend').run()
end
-- expect to find main.lua in appRoot if specified
if appRoot then
return run(appRoot, modPath or 'luigi')
end
-- try to find main.lua in a parent of this library, recursively.
local lastLibRoot = libRoot
repeat
if io.open(libRoot .. 'main.lua') then
return run(libRoot, modPath)
end
lastLibRoot = libRoot
libRoot = libRoot:gsub('([^/\\]*).$', function (m)
modPath = modPath and (m .. '.' .. modPath) or m
return ''
end)
until libRoot == lastLibRoot
error 'main.lua not found'

View File

@ -1,444 +0,0 @@
--[[--
A Layout contains a tree of widgets with a single `root` widget.
Layouts will resize to fit the window unless a `top` or `left`
property is found in the root widget.
Layouts are drawn in the order that they were shown, so the
most recently shown layout shown will always appear on top.
Other events are sent to layouts in the opposite direction,
and are trapped by the first layout that can handle the event
(for example, the topmost layer that is focused or hovered).
@classmod Layout
--]]--
local ROOT = (...):gsub('[^.]*$', '')
local Backend = require(ROOT .. 'backend')
local Base = require(ROOT .. 'base')
local Event = require(ROOT .. 'event')
local Widget = require(ROOT .. 'widget')
local Input = require(ROOT .. 'input')
local Style = require(ROOT .. 'style')
local Layout = Base:extend()
Layout.isLayout = true
--[[--
Layout constructor.
@function Luigi.Layout
@tparam table data
A tree of widget data.
@treturn Layout
A Layout instance.
--]]--
function Layout:constructor (data, master)
data = data or {}
if master then
self:setMaster(master)
else
self:setTheme(require(ROOT .. 'theme.light'))
self:setStyle()
end
self:addDefaultHandlers()
self.hooks = {}
self.isShown = false
self.root = data
Widget(self, data)
self.isReady = true
end
--[[--
Create a detached widget.
Internal function used to create widgets that are associated with
a layout, but "detached" from it.
Used by context menus, which use their "owner" widget's layout
for theme and style information but appear in a separate layout.
@tparam table data
A tree of widget data.
@treturn Widget
A widget instance.
--]]--
function Layout:createWidget (data)
return Widget(self, data)
end
local function clearWidget (widget)
widget.textData = nil
widget.fontData = nil
widget.position = {}
widget.dimensions = {}
widget.type = widget.type
for _, child in ipairs(widget) do
clearWidget(child)
end
local items = widget.items
if items then
for _, item in ipairs(items) do
clearWidget(item)
end
end
end
local function reset (self)
if not self.root then return end
clearWidget(self.root)
end
--[[--
Set the master layout for this layout.
This layout's theme and style will be set the same as the master layout, and
widgets added to this layout will be indexed and keyboard-accelerated by the
master layout instead of this layout.
@tparam Layout layout
Master layout
@treturn Layout Self
--]]--
function Layout:setMaster (layout)
self.master = layout
reset(self)
return self
end
--[[--
Set the style from a definition table or function.
@tparam table|function rules
Style definition.
@treturn Layout Self
--]]--
function Layout:setStyle (rules)
if type(rules) == 'function' then
rules = rules()
end
self.style = Style(rules or {}, { 'style' })
reset(self)
return self
end
--[[--
Set the theme from a definition table or function.
@tparam table|function rules
Theme definition.
--]]--
function Layout:setTheme (rules)
if type(rules) == 'function' then
rules = rules()
end
self.theme = Style(rules or {}, { 'type' })
reset(self)
return self
end
--[[--
Get the style from master layout or this layout.
@treturn table
Style table.
--]]--
function Layout:getStyle ()
return self.master and self.master:getStyle() or self.style
end
--[[--
Get the theme from master layout or this layout.
@treturn table
Theme table.
--]]--
function Layout:getTheme ()
return self.master and self.master:getTheme() or self.theme
end
--[[--
Show the layout.
Hooks all appropriate Love events and callbacks.
@treturn Layout
Return this layout for chaining.
--]]--
function Layout:show ()
if self.isShown then
Backend.hide(self)
self.isShown = nil
end
self.isShown = true
if not self.input then
self.input = Input.default -- Input(self)
end
Backend.show(self)
self.root:reshape()
Event.Show:emit(self, self)
return self
end
--[[--
Hide the layout.
Unhooks Love events and callbacks.
@treturn Layout
Return this layout for chaining.
--]]--
function Layout:hide ()
if not self.isShown then
return
end
self.isShown = nil
Backend.hide(self)
Event.Hide:emit(self, self)
return self
end
--[[--
Focus next focusable widget.
Traverses widgets using Widget:getNextNeighbor until a focusable widget is
found, and focuses that widget.
@treturn Widget
The widget that was focused, or nil
--]]--
function Layout:focusNextWidget ()
local widget = self.focusedWidget or self.root
local nextWidget = widget:getNextNeighbor()
while nextWidget ~= widget do
if nextWidget:focus() then return nextWidget end
nextWidget = nextWidget:getNextNeighbor()
end
end
--[[--
Focus previous focusable widget.
Traverses widgets using Widget:getPreviousNeighbor until a focusable widget is
found, and focuses that widget.
@treturn Widget
The widget that was focused, or nil
--]]--
function Layout:focusPreviousWidget ()
local widget = self.focusedWidget or self.root
local previousWidget = widget:getPreviousNeighbor()
while previousWidget ~= widget do
if previousWidget:focus() then return previousWidget end
previousWidget = previousWidget:getPreviousNeighbor()
end
end
--[[--
Get the innermost widget at given coordinates.
@tparam number x
Number of pixels from window's left edge.
@tparam number y
Number of pixels from window's top edge.
@tparam[opt] Widget root
Widget to search within, defaults to layout root.
--]]--
function Layout:getWidgetAt (x, y, root)
if not root then
root = self.root
end
-- Loop through in reverse, because siblings defined later in the tree
-- will overdraw earlier siblings.
for i = #root, 1, -1 do
local child = root[i]
if child:isAt(x, y) then
local inner = self:getWidgetAt(x, y, child)
if inner then return inner end
end
end
if root:isAt(x, y) then return root end
end
--[[--
Place a layout near a point or rectangle.
@tparam number left
Number of pixels from window's left edge.
@tparam number top
Number of pixels from window's top edge.
@tparam[opt] number width
Width of the rectangle to place layout outside of, defaults to 0.
@tparam[opt] number height
Height of the rectangle to place layout outside of, defaults to 0.
@treturn Layout
Return this layout for chaining.
--]]--
function Layout:placeNear (left, top, width, height)
width, height = width or 0, height or 0
local root = self.root
-- place to the left if there's no room to the right
local layoutWidth = root:getWidth()
local windowWidth, windowHeight = Backend.getWindowSize()
if left + width + layoutWidth > windowWidth then
left = left - layoutWidth - width
else
left = left + width
end
-- place above if there's no room below
local layoutHeight = root:getHeight()
if top + height + layoutHeight > windowHeight then
top = top - layoutHeight - height
else
top = top + height
end
root.left = left
root.top = top
end
-- Add handlers for keyboard shortcuts, tab focus, and mouse wheel scroll
function Layout:addDefaultHandlers ()
self.shortcuts = {}
for i = 0, 15 do
self.shortcuts[i] = {}
end
self.behavior = {}
local function createBehavior (name, hooks)
self.behavior[name] = hooks
function hooks.destroy ()
for _, hook in ipairs(hooks) do
hook:unhook()
end
self.behavior[name] = nil
end
end
createBehavior('context', {
self:onPressStart(function (event)
-- show context menu on right click
if event.button ~= 'right' then return end
local menu = event.target.context
if not menu then return end
menu:bubbleEvent('PressStart', event)
-- make sure it fits in the window
-- TODO: open in a new borderless window under SDL?
menu.menuLayout:placeNear(event.x - 1, event.y - 1, 2, 2)
return false
end)
})
createBehavior('shortcut', {
self:onKeyPress(function (event)
local entry = self.shortcuts[event.modifierFlags]
local widget = entry and entry[event.key]
if not widget then return end
widget.hovered = true
self.input:handlePressStart(self, 'left', event.x, event.y,
widget, widget.shortcut)
return false
end),
self:onKeyRelease(function (event)
local entry = self.shortcuts[event.modifierFlags]
local widget = entry and entry[event.key]
if not widget then return end
widget.hovered = false
self.input:handlePressEnd(self, 'left', event.x, event.y,
widget, widget.shortcut)
return false
end)
})
createBehavior('navigate', {
self:onKeyPress(function (event)
-- tab/shift-tab cycles focused widget
if event.key == 'tab' then
if Backend.isKeyDown('lshift', 'rshift') then
self:focusPreviousWidget()
else
self:focusNextWidget()
end
return false
end
-- space/enter presses focused widget
local widget = self.focusedWidget
if widget and event.key == 'space' or event.key == ' '
or event.key == 'return' then
self.input:handlePressStart(self, 'left', event.x, event.y,
widget, event.key)
return false
end
end),
self:onKeyRelease(function (event)
-- space / enter presses focused widget
local widget = self.focusedWidget
if widget and event.key == 'space' or event.key == ' '
or event.key == 'return' then
self.input:handlePressEnd(self, 'left', event.x, event.y,
widget, event.key)
return false
end
end)
})
createBehavior('scroll', {
self:onWheelMove(function (event)
if not event.hit then return end
local amount = event.scrollY ~= 0 and event.scrollY or event.scrollX
for widget in event.target:eachAncestor(true) do
if widget:scrollBy(amount) then return false end
end -- ancestor loop
return false
end) -- wheel move
})
createBehavior('status', {
self:onEnter(function (event)
local statusWidget = (self.master or self).statusWidget
if not statusWidget then return end
statusWidget.text = event.target.status
return false
end)
})
end
Event.injectBinders(Layout)
return Layout

View File

@ -1,110 +0,0 @@
--[[--
Mosiac, drawable class for 9-slice images.
@classmod Mosiac
--]]--
local ROOT = (...):gsub('[^.]*$', '')
local Backend = require(ROOT .. 'backend')
local Base = require(ROOT .. 'base')
local Mosaic = Base:extend()
local imageCache = {}
local sliceCache = {}
local function loadImage (path)
if not imageCache[path] then
imageCache[path] = Backend.Image(path)
end
return imageCache[path]
end
function Mosaic.fromWidget (widget)
local mosaic = widget.mosaic
if mosaic and mosaic.slicePath == widget.slices then
return mosaic
end
if widget.slices then
widget.mosaic = Mosaic(widget.slices)
return widget.mosaic
end
end
function Mosaic:constructor (path)
local slices = self:loadSlices(path)
self.batch = Backend.SpriteBatch(slices.image)
self.slices = slices
self.slicePath = path
end
function Mosaic:setRectangle (x, y, w, h)
if self.x == x and self.y == y and self.width == w and self.height == h then
self.needsRefresh = false
return
end
self.needsRefresh = true
self.x, self.y, self.width, self.height = x, y, w, h
end
function Mosaic:loadSlices (path)
local slices = sliceCache[path]
if not slices then
slices = {}
sliceCache[path] = slices
local image = loadImage(path)
local iw, ih = image:getWidth(), image:getHeight()
local w, h = math.floor(iw / 3), math.floor(ih / 3)
local Quad = Backend.Quad
slices.image = image
slices.width = w
slices.height = h
slices.topLeft = Quad(0, 0, w, h, iw, ih)
slices.topCenter = Quad(w, 0, w, h, iw, ih)
slices.topRight = Quad(iw - w, 0, w, h, iw, ih)
slices.middleLeft = Quad(0, h, w, h, iw, ih)
slices.middleCenter = Quad(w, h, w, h, iw, ih)
slices.middleRight = Quad(iw - w, h, w, h, iw, ih)
slices.bottomLeft = Quad(0, ih - h, w, h, iw, ih)
slices.bottomCenter = Quad(w, ih - h, w, h, iw, ih)
slices.bottomRight = Quad(iw - w, ih - h, w, h, iw, ih)
end
return slices
end
function Mosaic:draw ()
local batch = self.batch
if not self.needsRefresh then
Backend.draw(batch)
return
end
self.needsRefresh = false
local x, y, w, h = self.x, self.y, self.width, self.height
local slices = self.slices
local sw, sh = slices.width, slices.height
local xs = (w - sw * 2) / sw -- x scale
local ys = (h - sh * 2) / sh -- y scale
batch:clear()
batch:add(slices.middleCenter, x + sw, y + sh, 0, xs, ys)
batch:add(slices.topCenter, x + sw, y, 0, xs, 1)
batch:add(slices.bottomCenter, x + sw, y + h - sh, 0, xs, 1)
batch:add(slices.middleLeft, x, y + sh, 0, 1, ys)
batch:add(slices.middleRight, x + w - sw, y + sh, 0, 1, ys)
batch:add(slices.topLeft, x, y)
batch:add(slices.topRight, x + w - sw, y)
batch:add(slices.bottomLeft, x, y + h - sh)
batch:add(slices.bottomRight, x + w - sw, y + h - sh)
Backend.draw(batch)
end
return Mosaic

View File

@ -1,57 +0,0 @@
local Multiline = {}
function Multiline.wrap (font, text, limit)
local lines = {{ width = 0 }}
local advance = 0
local lastSpaceAdvance = 0
local function append (word, space)
local wordAdvance = font:getAdvance(word)
local spaceAdvance = font:getAdvance(space)
local words = lines[#lines]
if advance + wordAdvance > limit then
words.width = (words.width or 0) - lastSpaceAdvance
advance = wordAdvance + spaceAdvance
lines[#lines + 1] = { width = advance, word, space }
else
advance = advance + wordAdvance + spaceAdvance
words.width = advance
words[#words + 1] = word
words[#words + 1] = space
end
lastSpaceAdvance = spaceAdvance
end
local function appendFrag (frag, isFirst)
if isFirst then
append(frag, '')
else
local wordAdvance = font:getAdvance(frag)
lines[#lines + 1] = { width = wordAdvance, frag }
advance = wordAdvance
end
end
local leadSpace = text:match '^ +'
if leadSpace then
append('', leadSpace)
end
for word, space in text:gmatch '([^ ]+)( *)' do
if word:match '\n' then
local isFirst = true
for frag in (word .. '\n'):gmatch '([^\n]*)\n' do
appendFrag(frag, isFirst)
isFirst = false
end
append('', space)
else
append(word, space)
end
end
return lines
end
return Multiline

View File

@ -1,237 +0,0 @@
local ROOT = (...):gsub('[^.]*$', '')
local Backend = require(ROOT .. 'backend')
local Base = require(ROOT .. 'base')
local Event = require(ROOT .. 'event')
local Mosaic = require(ROOT .. 'mosaic')
local Text = Backend.Text
local Painter = Base:extend()
local imageCache = {}
-- local sliceCache = {}
function Painter:constructor (widget)
self.widget = widget
end
function Painter:loadImage (path)
if not imageCache[path] then
imageCache[path] = Backend.Image(path)
end
return imageCache[path]
end
function Painter:paintSlices ()
local widget = self.widget
local mosaic = Mosaic.fromWidget(widget)
if not mosaic then return end
local x, y, w, h = widget:getRectangle(true)
mosaic:setRectangle(x, y, w, h)
mosaic:draw()
end
function Painter:paintBackground ()
local widget = self.widget
if not widget.background then return end
local x, y, w, h = widget:getRectangle(true)
Backend.push()
Backend.setColor(widget.background)
Backend.drawRectangle('fill', x, y, w, h)
Backend.pop()
end
function Painter:paintOutline ()
local widget = self.widget
if not widget.outline then return end
local x, y, w, h = widget:getRectangle(true)
Backend.push()
Backend.setColor(widget.outline)
Backend.drawRectangle('line', x + 0.5, y + 0.5, w, h)
Backend.pop()
end
-- returns icon coordinates and rectangle with remaining space
function Painter:positionIcon (x1, y1, x2, y2)
local widget = self.widget
if not widget.icon then
return nil, nil, x1, y1, x2, y2
end
local icon = self:loadImage(widget.icon)
local iconWidth, iconHeight = icon:getWidth(), icon:getHeight()
local align = widget.align or ''
local padding = widget.padding or 0
local x, y
-- horizontal alignment
if align:find('right') then
x = x2 - iconWidth
x2 = x2 - iconWidth - padding
elseif align:find('center') then
x = x1 + (x2 - x1) / 2 - iconWidth / 2
else -- if align:find('left') then
x = x1
x1 = x1 + iconWidth + padding
end
-- vertical alignment
if align:find('bottom') then
y = y2 - iconHeight
elseif align:find('middle') then
y = y1 + (y2 - y1) / 2 - iconHeight / 2
else -- if align:find('top') then
y = y1
end
return x, y, x1, y1, x2, y2
end
-- returns text coordinates
function Painter:positionText (x1, y1, x2, y2)
local widget = self.widget
if not widget.text or x1 >= x2 then
return nil, nil, x1, y1, x2, y2
end
local font = widget:getFont()
local align = widget.align or ''
local horizontal = 'left'
-- horizontal alignment
if align:find 'right' then
horizontal = 'right'
elseif align:find 'center' then
horizontal = 'center'
elseif align:find 'justify' then
horizontal = 'justify'
end
if not widget.textData then
local limit = widget.wrap and x2 - x1 or nil
widget.textData = Text(
font, widget.text, widget.color, horizontal, limit)
end
local textHeight = widget.textData:getHeight()
local y
-- vertical alignment
if align:find('bottom') then
y = y2 - textHeight
elseif align:find('middle') then
y = y2 - (y2 - y1) / 2 - textHeight / 2
else -- if align:find('top') then
y = y1
end
return font, x1, y
end
function Painter:paintIconAndText ()
local widget = self.widget
if not (widget.icon or widget.text) then return end
local x, y, w, h = widget:getRectangle(true, true)
if w < 1 or h < 1 then return end
-- calculate position for icon and text based on alignment and padding
local iconX, iconY, x1, y1, x2, y2 = self:positionIcon(x, y, x + w, y + h)
local font, textX, textY = self:positionText(x1, y1, x2, y2)
local icon = widget.icon and self:loadImage(widget.icon)
local text = widget.text
local align = widget.align or ''
local padding = widget.padding or 0
-- if aligned center, icon displays above the text
-- reposition icon and text for proper vertical alignment
if icon and text and align:find('center') then
local iconHeight = icon:getHeight()
if align:find 'middle' then
local textHeight = widget.textData:getHeight()
local contentHeight = textHeight + padding + iconHeight
local offset = (h - contentHeight) / 2
iconY = y + offset
textY = y + offset + padding + iconHeight
elseif align:find 'top' then
iconY = y
textY = y + padding + iconHeight
else -- if align:find 'bottom'
local textHeight = widget.textData:getHeight()
textY = y + h - textHeight
iconY = textY - padding - iconHeight
end
end
-- horizontal alignment for non-wrapped text
-- TODO: handle this in Backend.Text
if text and not widget.wrap then
if align:find 'right' then
textX = textX + (w - widget.textData:getWidth())
elseif align:find 'center' then
textX = textX + (w - widget.textData:getWidth()) / 2
end
end
Backend.push()
Backend.intersectScissor(x, y, w, h)
-- draw the icon
if icon then
iconX, iconY = math.floor(iconX), math.floor(iconY)
Backend.draw(icon, iconX, iconY)
end
-- draw the text
if text and textX and textY and w > 1 then
widget.innerHeight = textY - y + widget.textData:getHeight()
widget.innerWidth = textX - x + widget.textData:getWidth()
textX = math.floor(textX - (widget.scrollX or 0))
textY = math.floor(textY - (widget.scrollY or 0))
Backend.draw(widget.textData, textX, textY)
end
Backend.pop()
end
function Painter:paintChildren ()
for i, child in ipairs(self.widget) do
child:paint()
end
end
function Painter:paint ()
local widget = self.widget
local x, y, w, h = widget:getRectangle()
-- if the drawable area has no width or height, don't paint
if w < 1 or h < 1 then return end
Event.PreDisplay:emit(widget, { target = widget }, function()
Backend.push()
if widget.parent then
Backend.intersectScissor(x, y, w, h)
else
Backend.setScissor()
end
self:paintBackground()
self:paintOutline()
self:paintSlices()
self:paintIconAndText()
self:paintChildren()
Backend.pop()
end)
Event.Display:emit(widget, { target = widget })
end
return Painter

View File

@ -1,99 +0,0 @@
--[[--
Keyboard shortcut module.
--]]--
local ROOT = (...):gsub('[^.]*$', '')
local Backend = require(ROOT .. 'backend')
local Shortcut = {}
local isMac = Backend.isMac()
local ALT = 1
local CTRL = 2
local SHIFT = 4
local GUI = 8
function Shortcut.appliesToPlatform (value)
if isMac and value:match '%f[%a]win%-'
or not isMac and value:match '%f[%a]mac%-' then
return false
end
return true
end
function Shortcut.expandAliases (value)
return value
:gsub('%f[%a]cmd%-', 'mac-gui-')
:gsub('%f[%a]command%-', 'mac-gui-')
:gsub('%f[%a]option%-', 'mac-alt-')
end
function Shortcut.parseKeyCombo (value)
-- expand command- and option- aliases
value = Shortcut.expandAliases(value)
-- exit early if shortcut is for different platform
if not Shortcut.appliesToPlatform(value) then return end
-- expand c- special modifier
if isMac then
value = value:gsub('%f[%a]c%-', 'gui-')
else
value = value:gsub('%f[%a]c%-', 'ctrl-')
end
-- extract main key
local mainKey = value:match '[^%-]*%-?$'
-- extract modifiers
local alt = value:match '%f[%a]alt%-' and ALT or 0
local ctrl = value:match '%f[%a]ctrl%-' and CTRL or 0
local shift = value:match '%f[%a]shift%-' and SHIFT or 0
local gui = value:match '%f[%a]gui%-' and GUI or 0
return mainKey, alt + ctrl + shift + gui
end
function Shortcut.getModifierFlags ()
local alt = Backend.isKeyDown('lalt', 'ralt') and ALT or 0
local ctrl = Backend.isKeyDown('lctrl', 'rctrl') and CTRL or 0
local shift = Backend.isKeyDown('lshift', 'rshift') and SHIFT or 0
local gui = Backend.isKeyDown('lgui', 'rgui') and GUI or 0
return alt + ctrl + shift + gui
end
function Shortcut.stringify (shortcut)
if type(shortcut) ~= 'table' then
shortcut = { shortcut }
end
for _, value in ipairs(shortcut) do
value = Shortcut.expandAliases(value)
if Shortcut.appliesToPlatform(value) then
if isMac then
value = value
:gsub('%f[%a]c%-', 'cmd-')
:gsub('%f[%a]gui%-', 'cmd-')
:gsub('%f[%a]alt%-', 'option-')
-- Have Love backend default to DejaVuSans
-- so we can use these instead of the above
--[[
:gsub('%f[%a]c%-', '')
:gsub('%f[%a]gui%-', '')
:gsub('%f[%a]alt%-', '')
:gsub('%f[%a]shift%-', '')
]]
else
value = value
:gsub('%f[%a]c%-', 'ctrl-')
:gsub('%f[%a]gui%-', 'windows-')
end
value = value:gsub('%f[%a]win%-', ''):gsub('%f[%a]mac%-', '')
value = value:gsub('%f[%w].', string.upper)
return value
end
end
end
return Shortcut

View File

@ -1,40 +0,0 @@
local ROOT = (...):gsub('[^.]*$', '')
local Base = require(ROOT .. 'base')
local Style = Base:extend()
function Style:constructor (rules, lookupNames)
self.rules = rules
self.lookupNames = lookupNames
end
function Style:getProperty (object, property, original)
local value = rawget(object, property)
if value ~= nil then return value end
local rules = self.rules
original = original or object
for _, lookupName in ipairs(self.lookupNames) do
local lookup = rawget(object, lookupName)
or object.attributes and rawget(object.attributes, lookupName)
if lookup then
if type(lookup) ~= 'table' then
lookup = { lookup }
end
for _, lookupValue in ipairs(lookup) do
local rule = rules[lookupValue]
if rule then
local value = self:getProperty(rule, property, original)
if type(value) == 'function' then
value = value(original)
end
if value ~= nil then return value end
end
end -- lookup values
end -- if lookup
end -- lookup names
end
return Style

View File

@ -1,12 +0,0 @@
local RESOURCE = (...):gsub('%.', '/') .. '/'
local ROOT = (...):gsub('[^.]*.[^.]*$', '')
return function (config)
config = config or {}
config.resources = config.resources or RESOURCE
config.backColor = config.backColor or { 40, 40, 40 }
config.lineColor = config.lineColor or { 60, 60, 60 }
config.textColor = config.textColor or { 240, 240, 240 }
config.highlight = config.highlight or { 0x00, 0x5c, 0x94 }
return require(ROOT .. 'engine.alpha')(config)
end

Binary file not shown.

Before

Width:  |  Height:  |  Size: 484 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 580 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 490 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 484 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 512 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 523 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 494 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 271 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 305 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 255 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 350 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 642 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 701 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 595 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 491 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 556 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 451 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 768 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 484 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 556 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 186 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 185 B

View File

@ -1,13 +0,0 @@
local RESOURCE = (...):gsub('%.', '/') .. '/'
local ROOT = (...):gsub('[^.]*.[^.]*$', '')
return function (config)
local theme = require(ROOT .. 'theme.light')()
theme.Control._defaultDimension = 44
theme.Line._defaultDimension = 32
theme.menu.height = 32
theme['menu.item'].height = 32
theme['menu.item'].padding = 8
theme.panel.padding = 8
return theme
end

View File

@ -1,12 +0,0 @@
local RESOURCE = (...):gsub('%.', '/') .. '/'
local ROOT = (...):gsub('[^.]*.[^.]*$', '')
return function (config)
config = config or {}
config.resources = config.resources or RESOURCE
config.backColor = config.backColor or { 240, 240, 240 }
config.lineColor = config.lineColor or { 220, 220, 220 }
config.textColor = config.textColor or { 0, 0, 0 }
config.highlight = config.highlight or { 0x19, 0xAE, 0xFF }
return require(ROOT .. 'engine.alpha')(config)
end

Binary file not shown.

Before

Width:  |  Height:  |  Size: 491 B

Some files were not shown because too many files have changed in this diff Show More