mirror of
https://github.com/cave-story-randomizer/cave-story-randomizer
synced 2024-11-10 00:45:15 +00:00
Made the patcher all pretty and shit.
This commit is contained in:
parent
58389f1872
commit
809f472a57
14
README.md
14
README.md
|
@ -1,9 +1,21 @@
|
|||
Cave Story Randomizer
|
||||
=====================
|
||||
|
||||
Todo
|
||||
----
|
||||
|
||||
- Add all weapons.
|
||||
- Add all items.
|
||||
- Add instructions.
|
||||
- Provide modified Cave.pxm. (When Bubbline or Fireball)
|
||||
|
||||
Issues
|
||||
------
|
||||
|
||||
- 3 Life Capsules can not be replaced because they appear on maps with 2 capsules. Need label-aware replace.
|
||||
- Hell Missile Upgrade uses a unique script and won't be easy to replace.
|
||||
- Bubbler (and other weapons?) can't break blocks in the First Cave.
|
||||
|
||||
Credits
|
||||
-------
|
||||
|
||||
- Font: https://datagoblin.itch.io/monogram
|
||||
|
|
BIN
src/assets/background.png
Normal file
BIN
src/assets/background.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.7 KiB |
BIN
src/assets/monogram_extended.ttf
Normal file
BIN
src/assets/monogram_extended.ttf
Normal file
Binary file not shown.
|
@ -2,9 +2,6 @@ if io then
|
|||
io.stdout:setvbuf("no")
|
||||
end
|
||||
|
||||
local seed = os.time()
|
||||
math.randomseed(seed)
|
||||
|
||||
function love.conf(t)
|
||||
t.window = {
|
||||
title = "Cave Story Randomizer",
|
||||
|
|
|
@ -1,10 +1,19 @@
|
|||
function weapon(t)
|
||||
assert(t.name and t.map and t.id and t.ammo)
|
||||
assert(t.name and t.map and t.id)
|
||||
local ammo = t.ammo or "0000"
|
||||
local names = t.name
|
||||
if type(names) == 'string' then
|
||||
names = {names}
|
||||
end
|
||||
local getText = {}
|
||||
for _, name in ipairs(names) do
|
||||
table.insert(getText, ("Got the =%s=!<WAI0160<NOD"):format(name))
|
||||
end
|
||||
return {
|
||||
name = t.name,
|
||||
name = names[1],
|
||||
map = t.map,
|
||||
getText = ("Got the =%s=!<WAI0160<NOD"):format(t.name),
|
||||
command = ("<AM+00%s:%s"):format(t.id, t.ammo),
|
||||
getText = getText,
|
||||
command = ("<AM+00%s:%s"):format(t.id, ammo),
|
||||
displayCmd = ("<GIT00%s"):format(t.id),
|
||||
music = "<CMU0010",
|
||||
kind = "weapon",
|
||||
|
@ -58,17 +67,24 @@ function item(t)
|
|||
end
|
||||
|
||||
return {
|
||||
-------------------
|
||||
-------------
|
||||
-- WEAPONS --
|
||||
-------------------
|
||||
-------------
|
||||
wPolarStar = weapon({
|
||||
name = "Polar Star",
|
||||
map = "Pole",
|
||||
id = "02",
|
||||
ammo = "0000",
|
||||
}),
|
||||
wBubbler = weapon({
|
||||
name = "Bubbler",
|
||||
wFireball = weapon({
|
||||
name = "Fireball",
|
||||
map = "Santa",
|
||||
id = "03",
|
||||
}),
|
||||
wBubbline = weapon({
|
||||
name = {
|
||||
"Bubbline",
|
||||
"Bubbler",
|
||||
},
|
||||
map = "Comu",
|
||||
id = "07",
|
||||
ammo = "0100",
|
||||
|
@ -156,7 +172,7 @@ return {
|
|||
mGrasslandsHut = missiles({
|
||||
map = "WeedB",
|
||||
}),
|
||||
mEggRuined = missiles({
|
||||
mEggCorridorRuined = missiles({
|
||||
map = "Eggs2",
|
||||
}),
|
||||
mEggObservationRuined = missiles({
|
||||
|
@ -173,6 +189,8 @@ return {
|
|||
-----------
|
||||
-- ITEMS --
|
||||
-----------
|
||||
-- - Map System
|
||||
-- - Chako's Rouge
|
||||
iPanties = item({
|
||||
name = "Curly's Panties",
|
||||
map = "CurlyS",
|
||||
|
@ -183,6 +201,20 @@ return {
|
|||
},
|
||||
music = "",
|
||||
}),
|
||||
-- - Turbocharge
|
||||
-- If you chose to take the Machine Gun from Curly in the Sand Zone, then you can
|
||||
-- receive this for free from the Gaudi shopkeeper, Chaba, in the Labyrinth. It
|
||||
-- speeds up the recovery rate of ammo for the Machine Gun.
|
||||
-- - Arms Barrier
|
||||
-- Halves weapon EXP lost when you take damage. Found the top part of the Camp,
|
||||
-- accessible via a hidden passageway in the ceiling in the large Labyrinth room
|
||||
-- from which you can access the normal Camp entrance and the Clinic nearby.
|
||||
-- Unless you took the Machine Gun, you'll have to come back to this area with the
|
||||
-- Booster to be able to reach it.
|
||||
-- - Whimsical Star
|
||||
-- A trinket that you can receive from Chaba, the Gaudi shopkeep in the Labyrinth,
|
||||
-- if you talk to him with the Spur weapon in your posession. It will cause small
|
||||
-- stars to float around you as a meagre shield when you charge the Spur to MAX.
|
||||
iLifePot = item({
|
||||
name = "Life Pot",
|
||||
map = "Cent",
|
||||
|
@ -194,10 +226,18 @@ return {
|
|||
|
||||
--[[
|
||||
|
||||
-- Weapons
|
||||
|
||||
<KEY<FLJ1640:0201<FL+1640<SOU0022<CNP0200:0021:0000
|
||||
<MSGOpened the chest.<NOD<GIT0002<AM+0002:0000<CLR
|
||||
<CMU0010Got the =Polar Star=!<WAI0160<NOD<GIT0000<CLO<RMU
|
||||
|
||||
#0500
|
||||
...
|
||||
Here, you can have this.<NOD<GIT0003<AM+0003:0000<CLR
|
||||
<CMU0010Got the =Fireball=!<WAI0160<NOD<RMU<GIT0000<CLRYou're looking for someone?<NOD
|
||||
|
||||
|
||||
#0301
|
||||
<KEY<GIT1008<MSGDo you want to use the
|
||||
=Jellyfish Juice=?<YNJ0000<CLO<GIT0000
|
||||
|
@ -206,6 +246,8 @@ return {
|
|||
ashes...<NOD<CLR<GIT0007<AM+0007:0100
|
||||
<CMU0010Got the =Bubbler=!<WAI0160<NOD<CLO<RMU<DNP0300<END
|
||||
|
||||
-- Life Capsules
|
||||
|
||||
<PRI<SOU0022<DNP0400<CMU0016
|
||||
<MSG<GIT1006Got a =Life Capsule=!<WAI0160<NOD<RMU<ML+0003
|
||||
Max health increased by 3!<NOD<END
|
||||
|
|
234
src/lib/terebi.lua
Normal file
234
src/lib/terebi.lua
Normal file
|
@ -0,0 +1,234 @@
|
|||
local Terebi = {
|
||||
_VERSION = 'terebi v0.4.0',
|
||||
_URL = 'https://github.com/oniietzschan/terebi',
|
||||
_DESCRIPTION = 'Graphics scaling library for Love2D.',
|
||||
_LICENSE = [[
|
||||
Massachusecchu... あれっ! Massachu... chu... chu... License!
|
||||
|
||||
Copyright (c) 1789 Retia Adolf
|
||||
|
||||
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 IZ】, 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. PLEASE HAVE A FUN AND BE GENTLE WITH THIS SOFTWARE.
|
||||
]]
|
||||
}
|
||||
|
||||
|
||||
|
||||
function Terebi.initializeLoveDefaults()
|
||||
love.graphics.setDefaultFilter('nearest', 'nearest')
|
||||
love.graphics.setLineStyle('rough')
|
||||
end
|
||||
|
||||
|
||||
|
||||
local Screen = {}
|
||||
local ScreenMetaTable = {__index = Screen}
|
||||
|
||||
function Terebi.newScreen(...)
|
||||
return setmetatable({}, ScreenMetaTable)
|
||||
:initialize(...)
|
||||
end
|
||||
|
||||
function Screen:initialize(width, height, scale)
|
||||
assert(type(scale) == 'number')
|
||||
|
||||
return self
|
||||
:setBackgroundColor(0, 0, 0)
|
||||
:setDimensions(width, height, scale)
|
||||
end
|
||||
|
||||
function Screen:getDimensions()
|
||||
return self._width, self._height
|
||||
end
|
||||
|
||||
function Screen:setDimensions(width, height, scale, isSkipWindowResize)
|
||||
assert(type(width) == 'number')
|
||||
assert(type(height) == 'number')
|
||||
scale = scale or self._scale
|
||||
|
||||
self._width = width
|
||||
self._height = height
|
||||
self._canvas = love.graphics.newCanvas(width, height)
|
||||
|
||||
return self
|
||||
:setScale(scale, isSkipWindowResize)
|
||||
:_saveScale()
|
||||
end
|
||||
|
||||
function Screen:setBackgroundColor(r, g, b)
|
||||
assert(type(r) == 'number')
|
||||
assert(type(g) == 'number')
|
||||
assert(type(b) == 'number')
|
||||
|
||||
self._backgroundColor = {r, g, b}
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function Screen:getScale()
|
||||
return self._scale
|
||||
end
|
||||
|
||||
function Screen:setScale(scale, isSkipWindowResize)
|
||||
assert(type(scale) == 'number')
|
||||
|
||||
self._scale = math.floor(math.max(1, math.min(scale, self:_getMaxScale())))
|
||||
|
||||
if isSkipWindowResize ~= true then
|
||||
self:_resizeWindow()
|
||||
end
|
||||
return self:_updateDrawOffset()
|
||||
end
|
||||
|
||||
function Screen:_resizeWindow()
|
||||
local currentW, currentH, flags = love.window.getMode()
|
||||
local newW, newH = love.window.fromPixels(self._width * self._scale, self._height * self._scale)
|
||||
if not love.window.getFullscreen() and (currentW ~= newW or currentH ~= newH) then
|
||||
love.window.setMode(newW, newH, flags)
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function Screen:_saveScale()
|
||||
self._savedScale = self._scale
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function Screen:_restoreScale()
|
||||
return self:setScale(self._savedScale)
|
||||
end
|
||||
|
||||
function Screen:increaseScale(isSkipWindowResize)
|
||||
return self
|
||||
:setScale(self._scale + 1, isSkipWindowResize)
|
||||
:_saveScale()
|
||||
end
|
||||
|
||||
function Screen:decreaseScale(isSkipWindowResize)
|
||||
return self
|
||||
:setScale(self._scale - 1, isSkipWindowResize)
|
||||
:_saveScale()
|
||||
end
|
||||
|
||||
function Screen:toggleFullscreen()
|
||||
if love.window.getFullscreen() then
|
||||
love.window.setFullscreen(false)
|
||||
self:_restoreScale()
|
||||
|
||||
else
|
||||
self:_saveScale()
|
||||
love.window.setFullscreen(true)
|
||||
self
|
||||
:setMaxScale()
|
||||
:_updateDrawOffset()
|
||||
end
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function Screen:setMaxScale()
|
||||
return self:setScale(self:_getMaxScale())
|
||||
end
|
||||
|
||||
function Screen:_getMaxScale()
|
||||
local desktopW, desktopH = self:_getDesktopDimensions()
|
||||
local maxScaleX = math.floor(desktopW / self._width)
|
||||
local maxScaleY = math.floor(desktopH / self._height)
|
||||
|
||||
return math.min(maxScaleX, maxScaleY)
|
||||
end
|
||||
|
||||
function Screen:handleResize()
|
||||
return self
|
||||
:setScale(self:_getMaxScaleForWindow(), true)
|
||||
:_updateDrawOffset()
|
||||
end
|
||||
|
||||
function Screen:_getMaxScaleForWindow()
|
||||
local w, h = love.window.getMode()
|
||||
return self:_getMaxScaleForDimensions(w, h)
|
||||
end
|
||||
|
||||
function Screen:_getMaxScaleForDimensions(w, h)
|
||||
local maxScaleX = math.floor(w / self._width)
|
||||
local maxScaleY = math.floor(h / self._height)
|
||||
|
||||
return math.min(maxScaleX, maxScaleY)
|
||||
end
|
||||
|
||||
function Screen:_updateDrawOffset()
|
||||
local w, h = love.window.getMode()
|
||||
local scaledWidth = self._width * self._scale
|
||||
local scaledHeight = self._height * self._scale
|
||||
self._drawOffsetX = math.floor((w - scaledWidth) / 2)
|
||||
self._drawOffsetY = math.floor((h - scaledHeight) / 2)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function Screen:_getDesktopDimensions()
|
||||
return love.window.toPixels(love.window.getDesktopDimensions())
|
||||
end
|
||||
|
||||
function Screen:draw(drawFunc, ...)
|
||||
assert(type(drawFunc) == 'function', type(drawFunc))
|
||||
|
||||
love.graphics.push('all')
|
||||
love.graphics.setCanvas(self._canvas)
|
||||
love.graphics.clear()
|
||||
drawFunc(...)
|
||||
love.graphics.pop()
|
||||
|
||||
-- Draw background if it would be visible
|
||||
if self._drawOffsetX ~= 0 or self._drawOffsetY ~= 0 then
|
||||
local r, g, b = love.graphics.getColor()
|
||||
love.graphics.setColor(unpack(self._backgroundColor))
|
||||
love.graphics.rectangle('fill', 0, 0, love.graphics.getDimensions())
|
||||
love.graphics.setColor(r, g, b)
|
||||
end
|
||||
-- Draw screen
|
||||
love.graphics.draw(self._canvas, self._drawOffsetX, self._drawOffsetY, 0, self._scale, self._scale)
|
||||
|
||||
return self
|
||||
end
|
||||
|
||||
function Screen:getMousePosition()
|
||||
return self:windowToScreen(love.mouse.getPosition())
|
||||
end
|
||||
|
||||
function Screen:windowToScreen(x, y)
|
||||
assert(type(x) == 'number')
|
||||
assert(type(y) == 'number')
|
||||
|
||||
return (x - self._drawOffsetX) / self._scale,
|
||||
(y - self._drawOffsetY) / self._scale
|
||||
end
|
||||
|
||||
function Screen:screenToWindow(x, y)
|
||||
assert(type(x) == 'number')
|
||||
assert(type(y) == 'number')
|
||||
|
||||
return x * self._scale + self._drawOffsetX,
|
||||
y * self._scale + self._drawOffsetY
|
||||
end
|
||||
|
||||
|
||||
|
||||
return Terebi
|
74
src/main.lua
74
src/main.lua
|
@ -3,8 +3,10 @@ require 'lib.strict'
|
|||
Class = require 'lib.classic'
|
||||
_ = require 'lib.moses'
|
||||
Serpent = require 'lib.serpent'
|
||||
Terebi = require 'lib.terebi'
|
||||
|
||||
lf = love.filesystem
|
||||
lg = love.graphics
|
||||
|
||||
local LOG_LEVEL = 3
|
||||
local function _log(level, prefix, text, ...)
|
||||
|
@ -29,15 +31,49 @@ do
|
|||
end
|
||||
end
|
||||
|
||||
local background
|
||||
local font
|
||||
local screen
|
||||
local status
|
||||
|
||||
function love.load()
|
||||
Terebi.initializeLoveDefaults()
|
||||
screen = Terebi.newScreen(320, 240, 2)
|
||||
background = lg.newImage('assets/background.png')
|
||||
font = lg.newFont('assets/monogram_extended.ttf', 16)
|
||||
font:setFilter('nearest', 'nearest', 1)
|
||||
status = "Drag and drop your Cave Story folder here."
|
||||
end
|
||||
|
||||
function love.directorydropped(path)
|
||||
-- Mount.
|
||||
local mountPath = 'mounted-data'
|
||||
assert(lf.mount(path, mountPath))
|
||||
local items = lf.getDirectoryItems('/' .. mountPath)
|
||||
local containsStage = _.contains(items, 'Stage')
|
||||
assert(containsStage)
|
||||
local dirStage = '/' .. mountPath .. '/Stage'
|
||||
local dirStage = '/' .. mountPath
|
||||
do
|
||||
local items = lf.getDirectoryItems(dirStage)
|
||||
local containsData = _.contains(items, 'data')
|
||||
if containsData then
|
||||
dirStage = dirStage .. '/data'
|
||||
end
|
||||
end
|
||||
do
|
||||
local items = lf.getDirectoryItems(dirStage)
|
||||
local containsStage = _.contains(items, 'Stage')
|
||||
if containsStage then
|
||||
dirStage = dirStage .. '/Stage'
|
||||
else
|
||||
status = "Could not find \"data\" subfolder.\n\nMaybe try dropping your Cave Story \"data\" folder in directly?"
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
-- Offer tribute to RNGesus.
|
||||
local seed = tostring(os.time())
|
||||
math.randomseed(seed)
|
||||
logNotice(('Offering seed "%s" to RNGesus'):format(seed))
|
||||
|
||||
-- Create TscFile objects.
|
||||
local tscFiles = {}
|
||||
for _, filename in ipairs(TSC_FILES) do
|
||||
local path = dirStage .. '/' .. filename
|
||||
|
@ -75,6 +111,13 @@ function love.directorydropped(path)
|
|||
-- Unmount.
|
||||
assert(lf.unmount(path))
|
||||
print("\n")
|
||||
|
||||
-- Update status
|
||||
status = [[Randomized data successfully created!
|
||||
|
||||
Next overwrite the files in your copy of Cave Story with the versions in the newly created "data" folder. Don't forget to save a backup of the originals!
|
||||
|
||||
Then play and have a fun!]]
|
||||
end
|
||||
|
||||
function love.keypressed(key)
|
||||
|
@ -82,3 +125,26 @@ function love.keypressed(key)
|
|||
love.event.push('quit')
|
||||
end
|
||||
end
|
||||
|
||||
local function _print(text, x, y, align)
|
||||
align = align or 'center'
|
||||
lg.setFont(font)
|
||||
local limit = 320 - (x * 2)
|
||||
lg.setColor(0, 0, 0)
|
||||
lg.printf(text, x + 1, y + 1, limit, align)
|
||||
lg.setColor(1, 1, 1)
|
||||
lg.printf(text, x, y, limit, align)
|
||||
end
|
||||
|
||||
local function _draw()
|
||||
lg.draw(background, 0, 0)
|
||||
_print('Cave Story Randomizer v0.1.0', 0, 10)
|
||||
_print('by shru', 0, 22)
|
||||
_print(status, 10, 65)
|
||||
_print('shru.itch.io', 10, 220, 'left')
|
||||
_print('@shruuu', 10, 220, 'right')
|
||||
end
|
||||
|
||||
function love.draw()
|
||||
screen:draw(_draw)
|
||||
end
|
||||
|
|
Loading…
Reference in a new issue