mirror of
https://github.com/cave-story-randomizer/cave-story-randomizer
synced 2025-03-25 11:29:29 +00:00
add patcher scripts
This commit is contained in:
parent
05409023cd
commit
bd8e4889fa
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -8,3 +8,5 @@ pre-edited-cs/Profile*
|
|||
pre-edited-cs/window\.rect
|
||||
|
||||
*Copy/
|
||||
|
||||
venv/
|
||||
|
|
60
caver/patcher.py
Normal file
60
caver/patcher.py
Normal file
|
@ -0,0 +1,60 @@
|
|||
from pathlib import Path
|
||||
from typing import Optional
|
||||
from lupa import LuaRuntime
|
||||
import logging
|
||||
import shutil
|
||||
pre_edited_cs = __import__("pre-edited-cs")
|
||||
|
||||
|
||||
CSVERSION = 3
|
||||
|
||||
def patch_files(patch_data: dict, output_dir: Path):
|
||||
ensure_base_files_exist(output_dir)
|
||||
|
||||
TscFile = LuaRuntime().eval(Path(__file__).parent.joinpath("tsc_file.lua").read_text())
|
||||
for mapname, mapdata in patch_data["maps"].items():
|
||||
patch_map(mapname, mapdata, TscFile, output_dir)
|
||||
|
||||
patch_mychar(patch_data["mychar"], output_dir)
|
||||
|
||||
patch_hash(patch_data["hash"], output_dir)
|
||||
|
||||
def ensure_base_files_exist(output_dir: Path):
|
||||
internal_copy = pre_edited_cs.get_path()
|
||||
|
||||
version = output_dir.joinpath("data", "Stage", "_version.txt")
|
||||
keep_existing_files = version.exists() and int(version.read_text()) >= CSVERSION
|
||||
|
||||
def should_ignore(path: Path, names: list[str]):
|
||||
if not keep_existing_files:
|
||||
return []
|
||||
return [path.joinpath(name) for name in names if path.joinpath(name).exists() and path.joinpath(name).is_file()]
|
||||
|
||||
shutil.copytree(internal_copy, output_dir, ignore=should_ignore)
|
||||
|
||||
def patch_map(mapname: str, mapdata: dict[str, dict], TscFile, output_dir: Path):
|
||||
mappath = output_dir.joinpath("data", "Stage", f"{mapname}.tsc")
|
||||
tsc_file = TscFile.new({}, mappath.read_bytes(), logging.getLogger("caver"))
|
||||
|
||||
for event, script in mapdata["pickups"].items():
|
||||
TscFile.placeItemAtLocation(tsc_file, script, event, mapname)
|
||||
|
||||
for event, song in mapdata["music"].items():
|
||||
TscFile.placeSongAtCue(tsc_file, song["song_id"], event, song["original_id"], mapname)
|
||||
|
||||
for event, script in mapdata["entrances"].items():
|
||||
TscFile.placeTraAtEntrance(tsc_file, script, event, mapname)
|
||||
|
||||
mappath.write_bytes(TscFile.getText(tsc_file))
|
||||
output_dir.joinpath("data", "Plaintext", f"{mapname}.txt")
|
||||
|
||||
def patch_mychar(mychar: Optional[str], output_dir: Path):
|
||||
if mychar is None:
|
||||
return
|
||||
mychar_img = Path(mychar).read_bytes()
|
||||
output_dir.joinpath("data", "MyChar.bmp").write_bytes(mychar_img)
|
||||
|
||||
def patch_hash(hash: list[int], output_dir: Path):
|
||||
hash_strings = [f"{num:04d}" for num in hash]
|
||||
hash_string = ",".join(hash_strings)
|
||||
output_dir.joinpath("data", "hash.txt").write_text(hash_string)
|
|
@ -1,49 +1,33 @@
|
|||
local C = Class:extend()
|
||||
local TscFile = {}
|
||||
|
||||
-- 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)
|
||||
|
||||
local file = lf.newFile(path)
|
||||
assert(file:open('r'))
|
||||
|
||||
local contents, size = file:read()
|
||||
function TscFile:new(contents, py_logging)
|
||||
self.log = py_logging
|
||||
self._text = self:_codec(contents, 'decode')
|
||||
|
||||
assert(file:close())
|
||||
assert(file:release())
|
||||
end
|
||||
|
||||
function C:hasUnreplacedItems()
|
||||
return #self._unreplaced >= 1
|
||||
end
|
||||
|
||||
function C:placeItemAtLocation(item, location)
|
||||
function TscFile:placeItemAtLocation(script, event, mapname)
|
||||
local wasChanged
|
||||
self._text, wasChanged = self:_stringReplace(self._text, "<EVE....", item.script, location.event)
|
||||
self._text, wasChanged = self:_stringReplace(self._text, "<EVE....", script, event)
|
||||
if not wasChanged then
|
||||
local template = 'Unable to place [%s] "%s" at "%s".'
|
||||
logError(template:format(location.map, item.name, location.name))
|
||||
local template = 'Unable to place script "%s" at [%s] event "%s".'
|
||||
self.log.error(template:format(script, mapname, event))
|
||||
end
|
||||
end
|
||||
|
||||
function C:placeSongAtCue(songid, event, map, originalid)
|
||||
function TscFile:placeSongAtCue(songid, event, originalid, mapname)
|
||||
local wasChanged
|
||||
self._text, wasChanged = self:_stringReplace(self._text, "<CMU" .. originalid, "<CMU" .. songid, event, {"<CMU0015", "<CMU0000"})
|
||||
if not wasChanged then
|
||||
local template = "Unable to replace [%s] event #%s's music cue with %q."
|
||||
logWarning(template:format(map, event, songid))
|
||||
self.log.warning(template:format(mapname, event, songid))
|
||||
end
|
||||
end
|
||||
|
||||
function C:_stringReplace(text, needle, replacement, label, overrides)
|
||||
function TscFile:placeTraAtEntrance(script, event, mapname)
|
||||
return -- TODO for entrance rando
|
||||
end
|
||||
|
||||
function TscFile:_stringReplace(text, needle, replacement, label, overrides)
|
||||
overrides = overrides or {}
|
||||
local pStart, pEnd = self:_getLabelPositionRange(label)
|
||||
|
||||
|
@ -53,11 +37,11 @@ function C:_stringReplace(text, needle, replacement, label, overrides)
|
|||
i = text:find(needle, pStart)
|
||||
|
||||
if i == nil then
|
||||
logDebug(('Unable to replace "%s" with "%s"'):format(needle, replacement))
|
||||
self.log.debug(('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))
|
||||
self.log.debug(('Found "%s", but was outside of label.'):format(needle, replacement))
|
||||
return text, false
|
||||
end
|
||||
|
||||
|
@ -88,7 +72,7 @@ function C:_stringReplace(text, needle, replacement, label, overrides)
|
|||
return a .. replacement .. b, true
|
||||
end
|
||||
|
||||
function C:_getLabelPositionRange(label)
|
||||
function TscFile:_getLabelPositionRange(label)
|
||||
local labelStart, labelEnd
|
||||
|
||||
-- Recursive shit for when label is a table...
|
||||
|
@ -128,7 +112,7 @@ function C:_getLabelPositionRange(label)
|
|||
end
|
||||
|
||||
if labelStart == nil then
|
||||
logError(("%s: Could not find label: %s"):format(self.mapName, label))
|
||||
self.log.error(("%s: Could not find label: %s"):format(self.mapName, label))
|
||||
labelStart = 1
|
||||
end
|
||||
|
||||
|
@ -139,18 +123,15 @@ function C:_getLabelPositionRange(label)
|
|||
return labelStart, labelEnd
|
||||
end
|
||||
|
||||
function C:writePlaintextTo(path)
|
||||
logInfo('writing Plaintext TSC to: ' .. path)
|
||||
U.writeFile(path, self._text)
|
||||
function TscFile:getPlaintext()
|
||||
return self._text
|
||||
end
|
||||
|
||||
function C:writeTo(path)
|
||||
logInfo('writing TSC to: ' .. path)
|
||||
local encoded = self:_codec(self._text, 'encode')
|
||||
U.writeFile(path, encoded)
|
||||
function TscFile:getText()
|
||||
return self:_codec(self._text, 'encode')
|
||||
end
|
||||
|
||||
function C:_codec(text, mode)
|
||||
function TscFile:_codec(text, mode)
|
||||
-- Create array of chars.
|
||||
local chars = {}
|
||||
text:gsub(".", function(c) table.insert(chars, c) end)
|
||||
|
@ -166,9 +147,9 @@ function C:_codec(text, mode)
|
|||
error('Unknown codec mode: ' .. tostring(mode))
|
||||
end
|
||||
|
||||
logDebug(" filesize", #chars)
|
||||
logDebug(" encoding char:", encodingChar)
|
||||
logDebug(" encoding char position:", encodingCharPosition)
|
||||
self.log.debug(" filesize", #chars)
|
||||
self.log.debug(" encoding char:", encodingChar)
|
||||
self.log.debug(" encoding char position:", encodingCharPosition)
|
||||
|
||||
-- Encode or decode.
|
||||
for pos, char in ipairs(chars) do
|
||||
|
@ -182,4 +163,4 @@ function C:_codec(text, mode)
|
|||
return decoded
|
||||
end
|
||||
|
||||
return C
|
||||
return TscFile
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
from pathlib import Path
|
||||
|
||||
|
||||
def get_path():
|
||||
return Path(__file__).parent
|
Loading…
Reference in a new issue