Substition is now label-aware. All life capsules are shuffled now.

This commit is contained in:
shru 2018-12-28 21:27:25 -05:00
parent 7bfb8b5dc0
commit 7decb15fb2
5 changed files with 148 additions and 266 deletions

View file

@ -4,7 +4,7 @@ Cave Story Randomizer
Todo
----
- Update to Love 11.2
- Generate correct directory structure for Cave Story+.
- Remove Love files... lovec.exe, readme, etc.
- Trade Sequence Step B: Require random obtainable weapon, instead of always polar star and fireball.
- Randomize Booster v0.8 / v2.0
@ -14,20 +14,15 @@ Issues
- [Speedrun] Random Mimiga in top left of Plantation seemingly gave +3? Probably a substition error with Life Capsule. https://youtu.be/PYhd9zhFdAk?t=5280
- [Speedrun] Snake in sisters fight https://youtu.be/PYhd9zhFdAk?t=4769
- [Speedrun] Best Ending events: Timer room and Curly's bed in plantation.
- Collecting the Super Missile Launcher increases your maximum missiles by 5. This does not normally happen.
- 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.
- Trading back the Nemesis for the Blade almost certainly will be weird.
- Ikachan Medal aquisition happens in black screen, so it doesn't make sense to show text box as it makes the player have to mash through blackness.
Bugs
----
- Sleeping with Chaco seemed to do nothing
- Jellyfish juice box told me I got a life capsule, then it said "Max HP increased by 5! Max HP increased by 3!" and then gave me the juice and my HP was actually the same. It's doing this every time I get the juice.
- Jenka gave me a life pot, but since I already had one, nothing happened. It even stayed in the same spot, instead of moving to the front.
- King died to give me a health capsule. The boss music resumed after I took it.
- btw, teleporter room key also caused the music glitch.
Credits
-------

View file

@ -26,6 +26,7 @@ function weapon(t)
music = "<CMU0010",
kind = "weapon",
replaceBefore = t.replaceBefore,
label = t.label,
}
end
@ -47,9 +48,12 @@ function equipment(t)
},
displayCmd = ("<GIT10%s"):format(t.id),
music = "<CMU0010",
-- Just erase <EQ+, since it's not always placed right after <IT+
-- We will add it again with command.
erase = ("<EQ+%s"):format(t.equipMask),
replaceBefore = {
-- Just erase <EQ+, since it's not always placed right after <IT+
-- We will add it again with command.
[("<EQ+%s"):format(t.equipMask)] = "",
},
label = t.label,
}
end
@ -78,6 +82,7 @@ function item(t)
displayCmd = ("<GIT10%s"):format(t.id),
music = t.music or "<CMU0010",
replaceBefore = t.replaceBefore,
label = t.label,
}
end
@ -99,10 +104,11 @@ function lifeCapsule(t)
command = ("<ML+000%d"):format(t.hp),
displayCmd = "<GIT1006",
music = t.music or "<CMU0016",
erase = {
("Max health increased by %d!<NOD"):format(t.hp),
("Max life increased by %d.<NOD"):format(t.hp), -- Cave Story+
replaceBefore = {
[("Max health increased by %d!<NOD"):format(t.hp)] = "",
[("Max life increased by %d.<NOD"):format(t.hp)] = "", -- Cave Story+
},
label = t.label,
}
end
@ -128,6 +134,7 @@ function missiles(t)
["<EVE0030"] = "<CMU0010<GIT0006*MISSILE_TEXT*<RMU<EVE0030<END",
},
kind = "missiles",
label = t.label,
}
end
@ -138,11 +145,13 @@ local data = {
wPolarStar = weapon({
name = "Polar Star",
map = "Pole",
label = "0200",
id = "02",
}),
wFireball = weapon({
name = "Fireball",
map = "Santa",
label = "0500",
id = "03",
}),
wBubbline = weapon({
@ -151,51 +160,52 @@ local data = {
"Bubbler",
},
map = "Comu",
label = "0301",
id = "07",
ammo = "0100",
}),
wMachineGun = weapon({
name = "Machine Gun",
map = "Curly",
label = "0415",
id = "04",
ammo = "0100",
replaceBefore = {
["<TAM0002:"] = "<AM-0002<AM+",
},
label = "0415",
}),
wBlade = weapon({
name = "Blade",
map = "Gard",
id = "09",
label = "0601",
id = "09",
}),
wSnake = weapon({
name = "Snake",
map = "MazeA",
label = "0401",
id = "01",
replaceBefore = {
["<TAM0002:"] = "<AM-0002<AM+",
},
label = "0401",
}),
wSpur = weapon({
name = "Spur",
map = "Pole",
label = "0302",
id = "13",
replaceBefore = {
["<TAM0002:"] = "<AM-0002<AM+",
},
label = "0302",
}),
wNemesis = weapon({
name = "Nemesis",
map = "Little",
label = "0200",
id = "12",
replaceBefore = {
["<TAM0009:"] = "<AM-0009<AM+",
},
label = "0200",
}),
---------------
@ -204,30 +214,30 @@ local data = {
eMapSystem = equipment({
name = "Map System",
map = "Mimi",
label = "0200",
id = "02",
equipMask = "0002",
label = "0200",
}),
eTurbocharge = equipment({
name = "Turbocharge",
map = "MazeA",
label = "0402",
id = "20",
equipMask = "0008",
label = "0402",
}),
eWhimsicalStar = equipment({
name = "Whimsical Star",
map = "MazeA",
label = "0404",
id = "38",
equipMask = "0128",
label = "0404",
}),
eArmsBarrier = equipment({
name = "Arms Barrier",
map = "MazeO",
label = "0400",
id = "19",
equipMask = "0004",
label = "0400",
}),
-----------
@ -239,9 +249,9 @@ local data = {
"Chako's Rouge", -- Probably doesn't matter, since no text?
},
map = "Chako",
label = "0211",
id = "37",
music = "",
label = "0211",
replaceBefore = {
-- Display Item.
[".....<IT+0037<NOD"] = "<GIT1037.....<IT+0037",
@ -253,14 +263,20 @@ local data = {
"Curly's Underwear",
},
map = "CurlyS",
label = "0420",
id = "35",
music = "",
}),
iLifePot = item({
name = "a Life Pot",
name = "Life Pot",
map = "Cent",
id = "15",
label = "0450",
id = "15",
replaceBefore = {
-- Delete check for Life Pot so that 2nd item is always given.
["<ITJ0015:0451"] = "",
},
}),
iAlienMedal = item({
name = "Alien Medal",
@ -277,8 +293,8 @@ local data = {
"Medal of the Red Ogre",
},
map = "Priso2",
id = "31",
label = "0251",
id = "31",
replaceBefore = {
-- Remove achievement trigger which appears in Cave Story+.
["<ACH0041"] = "",
@ -293,64 +309,58 @@ local data = {
lFirstCave = lifeCapsule({
hp = 3,
map = "Cave",
label = "????"
label = "0400"
}),
lYamashitaFarm = lifeCapsule({
hp = 3,
map = "Plant",
label = "????"
label = "0400"
}),
lEggCorridorA = lifeCapsule({
hp = 3,
map = "Eggs",
label = "????"
label = "0400"
}),
lEggCorridorB = lifeCapsule({
hp = 4,
map = "Eggs",
label = "0401"
}),
-- !!!! Can't randomize this one until I do some label-aware shit !!!!
-- [Egg Corridor] Go through Cthulhu's Abode and out the top door. +4 HP.
-- lEggCorridorB = lifeCapsule({
-- hp = 4,
-- map = "Eggs",
-- label = "0401"
-- }),
lGrasstown = lifeCapsule({
hp = 5,
map = "Weed",
label = "????"
label = "0305"
}),
lGrasstownExecution = lifeCapsule({
hp = 5,
map = "WeedD",
label = "????"
label = "0304"
}),
lSandZoneA = lifeCapsule({
hp = 5,
map = "Sand",
label = "0500"
}),
-- !!!! Can't randomize this one until I do some label-aware shit !!!!
-- [Sand Zone] At the end of the hidden path behind a pawpad block on the far right. +5 HP.
-- lSandZoneB = lifeCapsule({
-- hp = 4,
-- map = "Sand",
-- label = "0501"
-- }),
lSandZoneB = lifeCapsule({
hp = 5,
map = "Sand",
label = "0501"
}),
lLabyrinth = lifeCapsule({
hp = 5,
map = "MazeI",
label = "????"
label = "0300"
}),
lPlantationA = lifeCapsule({
hp = 5,
map = "Cent",
label = "0450"
}),
-- !!!! Can't randomize this one until I do some label-aware shit !!!!
-- [Plantation] Sitting on a platform hanging from the far upper-left ceiling. +4 HP.
-- lPlantationB = lifeCapsule({
-- hp = 4,
-- map = "Cent",
-- label = "0500"
-- }),
lPlantationB = lifeCapsule({
hp = 4,
map = "Cent",
label = "0500"
}),
lHell = lifeCapsule({
hp = 5,
map = "Hell1",
@ -363,15 +373,19 @@ local data = {
--------------
mEggObservation = missiles({
map = "EggR",
label = "0300",
}),
mGrasslands = missiles({
map = "Weed",
label = "0302",
}),
mGrasslandsHut = missiles({
map = "WeedB",
label = "0300",
}),
mEggCorridorRuined = missiles({
map = "Eggs2",
label = "0320",
}),
mEggObservationRuined = missiles({
map = "EggR2",
@ -380,6 +394,7 @@ local data = {
mSuperMissileLauncher = {
name = "Super Missile Launcher",
map = "MazeS",
label = {"0200", "0201"},
getText = {
"Got the =Super Missile Launcher=!<WAI0160<NOD", -- Gameplay text
"Your Missiles have been powered up!<WAI0160<NOD", -- Actual text
@ -393,18 +408,17 @@ local data = {
displayCmd = "<GIT0010",
music = "<CMU0010",
replaceBefore = {
["<FL+0202"] = "", -- Flag will be set as part of command instead.
["<FLJ0201:0201"] = "<EVE0201", -- Skip check which ensures you already have the missiles.
["<TAM0005:0010:0000"] = "<AM+0005:0005<TAM0005:0010:0000<FL+0202",
},
erase = "<FL+0202",
label = "0201", -- Also 0200...
}
-- !!!! Uses a unique script... will have to be crafty... !!!!
-- [Sanctuary] Bonus expansion. Just before you fight the Heavy Press at the
-- far west end of Blood-Stained Sanctuary B3, just past the pillar with 3 Deletes
-- covering it, in the room filled with flying Butes and with a hanging platform
-- with an arrow Bute on either side. Above this platform is a single Star Block
-- concealing a chest containing this massive expansion of 24 Misisles.
-- concealing a chest containing this massive expansion of 24 Missiles.
}
for k, t in pairs(data) do
@ -412,198 +426,3 @@ for k, t in pairs(data) do
end
return data
--[[
-- 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
<IT-0008<ANP0300:0010:0000<WAI0030<FLJ0442:0302<FL+0442
<MSGYou find something in the
ashes...<NOD<CLR<GIT0007<AM+0007:0100
<CMU0010Got the =Bubbler=!<WAI0160<NOD<CLO<RMU<DNP0300<END
#0415
<KEY<MSG<FAC0019Oh, wow.<NOD<CLRThat Polar Star of yours
is in awful shape.<NOD
Do you want to trade
it for my machine gun?<YNJ0420<FL+0563<FAC0000<CLR
<TAM0002:0004:0100<GIT0002Handed over the =Polar Star=.<NOD<CLR
<CMU0010<GIT0004Got the =Machine Gun=!<WAI0160<NOD<RMU<CLO
<FAO0004<TRA0029:0090:0012:0009
#0601
<KEY<FL-0621<DNP0505<AM+0009:0000
<MSG<GIT0009<CMU0010
Got the =Blade=.<WAI0160<NOD<RMU<END
#0400
<KEY<FLJ0721:0410<MSGHey there.<NOD<CLRThis is the Labyrinth Shop!<NOD
But, sad to say, we got
burgled a while back,<NOD
and there's nothing to sell
right now.<NOD
Sorry 'bout that...<NOD<CLO<AMJ0002:0401<AMJ0013:0404<EVE0402
#0401
<KEY<MSGHm?<NOD<CLRHey, you've got something
pretty spiffy there.<NOD<CLRA Polar Star and a Fireball,
unless I miss my guess.<NOD<CLRCan I take a quick look at them?<YNJ0403<CLO
<AM-0003
<WAI0020<MSG<GIT0002Handed over the Polar Star.<NOD
<GIT0003Handed over the Fireball.<NOD<GIT0000<CLR
<SOU0044Hoho!<NOD<CLR<FL+0721<GIT0001<CLR
<TAM0002:0001:0000<CMU0010=Snake= complete!<WAI0160<NOD<RMU<END
#0402
<KEY<FL+0721<MSG*sigh*<NOD<CLRHere. How about this?<NOD<CLR<GIT1020<CLR
<CMU0010<IT+0020<EQ+0008Got the =Turbocharge=!<WAI0160<NOD<RMU<GIT0000<CLRYou can have it for free.<NOD
I don't see any money on you,
anyway.<NOD<END
#0403
<KEY<MSG<CLRYou're missing out!<NOD<END
#0404
<KEY<FL+0721<MSG*sigh*<NOD<CLRHere. How about this?<NOD<CLR<GIT1038<CLR
<CMU0010<IT+0038Got the =Whimsical Star=!<WAI0160<EQ+0128<FL+0722<NOD<RMU<GIT0000<CLRJust a decoration, I'm afraid,<NOD
but you've already got the
strongest weapon, so what
else can I do?<NOD<END
#0302
...
You can keep this gun.<NOD<CMU0000<FAO0001
After I finish it, of course.<NOD<CLO
<WAI0150<FAI0001
<FLA<WAI0050<TAM0002:0013:0000<FL+1644<FL+0303<MSG
<CMU0010<MSG<GIT0013
=Polar Star= became the =Spur=!<SMC<DNP0210<WAI0160<NOD<CMU0008<END
#0200
...
if that isn't a fine-
looking blade you've got
there.<NOD
Care to trade it for my fabulous
gun?<YNJ0201<FL+1372<CLR
<TAM0009:0012:0000<GIT0009Gave him the =Blade=.<NOD<CLR
<CMU0010<GIT0012Got the =Nemesis=!<WAI0160<NOD<RMU<END
#0201
<PRI<MSGReally? Too bad.<NOD<END
-- Equipment
#0200
<PRI<FLJ0322:0201<FL+0322<SOU0022<CNP0200:0021:0000
<MSGOpened the chest.<NOD<GIT1002<IT+0002<EQ+0002<CLR
<CMU0010Got the =Map System=!<WAI0160<NOD<RMU<EVE0201
#0400
<KEY<FLJ0705:0001<FL+0705<SOU0022<CNP0400:0021:0000
<MSGOpened the treasure chest.<NOD<GIT1019<IT+0019<EQ+0004<CLR
<CMU0010Got the =Arms Barrier=!<WAI0160<NOD<RMU<END
-- Items
-- Chako's Rouge
#0211
<KEY<MSG
Do you want to rest?<YNJ0000<FAO0004<CMU0000<WAI0020<CLR.....<IT+0037<NOD<CLO
<MNP0300:0012:0006:0000<ANP0300:0010:0000
<WAI0050
<LI+1000<SOU0020<MYD0002<MSG
Health restored.<NOD<CLO<RMU<FAI0004<END
-- Life Capsules
<PRI<SOU0022<DNP0400<CMU0016
<MSG<GIT1006Got a =Life Capsule=!<WAI0160<NOD<RMU<ML+0003
Max health increased by 3!<NOD<END
#0400
<PRI<FL+0101<SOU0022<DNP0400<CMU0016
<MSG<GIT1006Got a =Life Capsule=!<WAI0160<NOD<RMU<ML+0003
Max health increased by 3!<NOD<END
#0305
<PRI<DNP0305<SOU0022<CMU0016
<MSG<GIT1006Got a =Life Capsule=!<WAI0160<NOD<RMU<ML+0005
Max health increased by 5!<NOD<END
#0304
<PRI<DNP0304<SOU0022<CMU0016
<MSG<GIT1006Got a =Life Capsule=!<WAI0160<NOD<RMU<ML+0005
Max health increased by 5!<NOD<END
#0300
<PRI<SOU0022<DNP0300<CMU0016
<MSG<GIT1006Got a =Life Capsule=!<WAI0160<NOD<RMU<ML+0005
Max health increased by 5!<NOD<END
#0450
<PRI<FLJ1040:0451<FL+1040<MSGLong time no arf!<NOD<CLRI was told to bring this
to you...<NOD<CLR<SOU0022<CMU0016<GIT1006Got a =Life Capsule=!<WAI0160<NOD<RMU<ML+0005
Max health increased by 5!<NOD<GIT0000<ITJ0015:0451<CLROh, she said to give you
this, too...<NOD<CLR
<CMU0010<GIT1015<IT+0015<GIT1015Got a =Life Pot=!<WAI0160<NOD<GIT0000<RMU<EVE0451
<END
#0500
<PRI<SOU0022<DNP0500<CMU0016
<MSG<GIT1006Got a =Life Capsule=!<WAI0160<NOD<RMU<ML+0004
Max health increased by 4!<NOD<END
#0400
<PRI<SOU0022<DNP0400
<MSG<GIT1006Got a =Life Capsule=!<NOD<ML+0005
Max health increased by 5!<NOD<END
<KEY<DNP0420<MSG<GIT1035<IT+0035
Found Curly's Panties.<NOD<END
<CMU0010<GIT1015<IT+0015<GIT1015Got a =Life Pot=!<WAI0160<NOD<GIT0000<RMU<EVE0451
<END
-- TODO:
#0401
<PRI<FL+0102<SOU0022<DNP0401<CLR<CMU0016
<MSG<GIT1006Got a =Life Capsule=!<WAI0160<NOD<RMU<ML+0004
Max health increased by 4!<NOD<END
-- Missiles
#0300
<PRI<FLJ0200:0001<FL+0200
<SOU0022<CNP0300:0021:0000
<MSGOpened the treasure chest.<NOD<CLR<EVE0030
#0302
<PRI<FLJ0218:0001<FL+0218
<SOU0022<CNP0302:0021:0000
<MSGOpened the treasure chest.<NOD<EVE0030
#0200
<PRI<FLJ0201:0201<MSGA charm has been placed on it.
It won't open...<NOD<END
#0201
<FLJ0766:0001<FL+0766<FL-0765<FL+0202
<SOU0022<CNP0200:0021:0000
<MSGOpened the treasure chest.<NOD<CLR<TAM0005:0010:0000
<CMU0010<GIT0010Your Missiles have been powered up!<WAI0160<NOD<RMU<END
]]

View file

@ -1,6 +1,6 @@
require 'lib.strict'
local VERSION = '0.5'
VERSION = '0.6'
Class = require 'lib.classic'
_ = require 'lib.moses'

View file

@ -22,6 +22,7 @@ local WEAPONS_WHICH_CAN_NOT_BREAK_BLOCKS = {
function C:randomize(path)
resetLog()
logNotice('=== Cave Story Randomizer v' .. VERSION .. ' ===')
local success, dirStage = self:_mountDirectory(path)
if not success then
return "Could not find \"data\" subfolder.\n\nMaybe try dropping your Cave Story \"data\" folder in directly?"

View file

@ -2,6 +2,12 @@ local C = Class:extend()
local ITEM_DATA = require 'database.items'
local OPTIONAL_REPLACES = {
'Max health increased by ',
'Max life increased by ',
'<ACH0041', -- Cave Story+ only, trigger achievement.
}
function C:new(path)
logInfo('reading TSC: ' .. path)
@ -53,21 +59,26 @@ function C:replaceSpecificItem(originalKey, replacement)
local template = "[%s] %s -> %s"
logNotice(template:format(self._mapName, original.name, replacement.name))
-- Replace before
-- Replace before.
if original.replaceBefore then
for needle, replacement in pairs(original.replaceBefore) do
self._text = self:_stringReplace(self._text, needle, replacement)
end
end
local wasChanged
self._text, wasChanged = self:_stringReplace(self._text, needle, replacement, original.label)
-- Erase first, in case replace attribute would place some text that would match here...
if original.erase then
local erases = original.erase
if type(erases) == 'string' then
erases = {erases}
end
for _, erase in ipairs(erases) do
self._text = self:_stringReplace(self._text, erase, '')
-- Log error if replace was not optional.
if wasChanged == false then
local wasOptional = false
for _, pattern in ipairs(OPTIONAL_REPLACES) do
if needle:find(pattern, 1, true) then
wasOptional = true
break
end
end
if wasOptional == false then
local template = 'Unable to replace [%s] "%s" with "%s".'
logError(template:format(original.map, needle, replacement))
end
end
end
end
@ -101,7 +112,7 @@ function C:_replaceAttribute(original, replacement, attribute)
break -- continue
end
local changed
self._text, changed = self:_stringReplace(self._text, originalText, replaceText)
self._text, changed = self:_stringReplace(self._text, originalText, replaceText, original.label)
if changed then
return
end
@ -112,11 +123,16 @@ function C:_replaceAttribute(original, replacement, attribute)
logMethod(template:format(attribute, original.map, original.name))
end
function C:_stringReplace(text, needle, replacement)
local i = text:find(needle, 1, true)
function C:_stringReplace(text, needle, replacement, label)
local pStart, pEnd = self:_getLabelPositionRange(label)
local i = text:find(needle, pStart, true)
if i == nil then
-- logWarning(('Unable to replace "%s" with "%s"'):format(needle, replacement))
return text, false
elseif i > pEnd then
-- This is totally normal and can be ignored.
-- logDebug(('Found "%s", but was outside of label.'):format(needle, replacement))
return text, false
end
local len = needle:len()
local j = i + len - 1
@ -127,6 +143,57 @@ function C:_stringReplace(text, needle, replacement)
return a .. replacement .. b, true
end
function C:_getLabelPositionRange(label)
local labelStart, labelEnd
-- Recursive shit for when label is a table...
if type(label) == 'table' then
labelStart, labelEnd = math.huge, 0
for _, _label in ipairs(label) do
local _start, _end = self:_getLabelPositionRange(_label)
labelStart = math.min(labelStart, _start)
labelEnd = math.max(labelEnd, _end)
end
return labelStart, labelEnd
end
assert(type(label) == 'string')
assert(#label == 4)
assert(tonumber(label) >= 1)
assert(tonumber(label) <= 9999)
local i = 1
local labelPattern = "#%d%d%d%d\r\n"
while true do
local j = self._text:find(labelPattern, i)
if j == nil then
break
end
i = j + 1
if labelStart then
labelEnd = j - 1
break
end
local _label = self._text:sub(j + 1, j + 4)
if label == _label then
labelStart = j
end
end
if labelStart == nil then
logError("Could not find label: " .. label)
labelStart = 1
end
if labelEnd == nil then
labelEnd = #self._text
end
return labelStart, labelEnd
end
function C:writePlaintextTo(path)
logInfo('writing Plaintext TSC to: ' .. path)
U.writeFile(path, self._text)