mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-12 08:21:02 +02:00
Merge branch 'dev' into feat/switch-nx
Bring feat/switch-nx up to date with origin/dev (72 commits). Resolve Input/RomImporter conflicts by keeping GamepadMap (NX face remap + dual-path gate) while adopting upstream joyBindings rebinds (#632) and Enable-all mods (#647). Gate shoulder GAME SPEED hotkeys when Select is held so Select+L display chords still work. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -461,6 +461,27 @@ def extract_sprites(rom, symbols, manifest, out_dir, assets_dir):
|
||||
"walker": bike_frames >= 6,
|
||||
}
|
||||
|
||||
# Yellow-only: the surfing-Pikachu overworld ride sheet, loaded
|
||||
# outside SpriteSheetPointerTable (LoadSurfingPlayerSpriteGraphics2) --
|
||||
# same extra extract as RedBikeSprite. (RFC 0001)
|
||||
surf = manifest["sprites"].get("surfPikachu")
|
||||
if surf and _has_symbol(symbols, surf["label"]):
|
||||
surf_symbol = _symbol(symbols, surf["label"])
|
||||
surf_length = surf["imageWidth"] * surf["imageHeight"] // 4
|
||||
_write_2bpp_png(
|
||||
rom.bytes(surf_symbol.bank, surf_symbol.address, surf_length),
|
||||
surf["imageWidth"], surf["imageHeight"],
|
||||
os.path.join(assets_dir, "sprites", surf["imageBase"] + ".png"),
|
||||
transparent_color0=True)
|
||||
surf_frames = surf["imageHeight"] // 16
|
||||
out["SPRITE_SURFING_PIKACHU"] = {
|
||||
"id": "SPRITE_SURFING_PIKACHU",
|
||||
"source": f"ROM:{surf['label']}",
|
||||
"image": f"assets/generated/sprites/{surf['imageBase']}.png",
|
||||
"frames": surf_frames,
|
||||
"walker": surf_frames >= 6,
|
||||
}
|
||||
|
||||
util.write_lua(
|
||||
os.path.join(out_dir, "sprites.lua"), out,
|
||||
header="Source: canonical Pokemon Red ROM (overworld sprite sheets)")
|
||||
|
||||
@@ -83,6 +83,23 @@ def extract(pokered, out_dir, assets_dir, sprite_order):
|
||||
"walker": frames >= 6,
|
||||
}
|
||||
|
||||
# Yellow-only: the surfing-Pikachu overworld ride sheet, loaded
|
||||
# outside SpriteSheetPointerTable (LoadSurfingPlayerSpriteGraphics2) --
|
||||
# same extra extract as RedBikeSprite. (RFC 0001)
|
||||
surf_src = files.get("SurfingPikachuSprite")
|
||||
if surf_src:
|
||||
png_src = os.path.join(pokered, re.sub(r"\.2bpp$", ".png", surf_src))
|
||||
dst = os.path.join(assets_dir, "sprites", "surfing_pikachu.png")
|
||||
size = gfx.convert_png(png_src, dst, transparent_color0=True)
|
||||
frames = size[1] // 16
|
||||
out["SPRITE_SURFING_PIKACHU"] = {
|
||||
"id": "SPRITE_SURFING_PIKACHU",
|
||||
"source": "gfx/sprites.asm SurfingPikachuSprite (LoadSurfingPlayerSpriteGraphics2)",
|
||||
"image": "assets/generated/sprites/surfing_pikachu.png",
|
||||
"frames": frames,
|
||||
"walker": frames >= 6,
|
||||
}
|
||||
|
||||
util.write_lua(os.path.join(out_dir, "sprites.lua"), out,
|
||||
header="Sources: data/sprites/sprites.asm, gfx/sprites/*.png\n"
|
||||
"Frame order (walker): stand D/U/L, walk D/U/L; right = flipped left.")
|
||||
|
||||
@@ -280,7 +280,7 @@ def sprite_metadata(pokered, order):
|
||||
bike_png = re.sub(r"\.2bpp$", ".png", bike_source)
|
||||
with Image.open(os.path.join(pokered, bike_png)) as image:
|
||||
bike_width, bike_height = image.size
|
||||
return {
|
||||
out_manifest = {
|
||||
"order": out,
|
||||
"bike": {
|
||||
"label": "RedBikeSprite",
|
||||
@@ -289,6 +289,20 @@ def sprite_metadata(pokered, order):
|
||||
"imageHeight": bike_height,
|
||||
},
|
||||
}
|
||||
# Yellow-only: the surfing-Pikachu overworld ride sheet, parallel
|
||||
# to the bike entry; absent in Red/Blue. (RFC 0001)
|
||||
surf_source = files.get("SurfingPikachuSprite")
|
||||
if surf_source:
|
||||
surf_png = re.sub(r"\.2bpp$", ".png", surf_source)
|
||||
with Image.open(os.path.join(pokered, surf_png)) as image:
|
||||
surf_width, surf_height = image.size
|
||||
out_manifest["surfPikachu"] = {
|
||||
"label": "SurfingPikachuSprite",
|
||||
"imageBase": "surfing_pikachu",
|
||||
"imageWidth": surf_width,
|
||||
"imageHeight": surf_height,
|
||||
}
|
||||
return out_manifest
|
||||
|
||||
|
||||
def text_metadata(pokered):
|
||||
|
||||
@@ -103,6 +103,10 @@ YELLOW_EXTRA_SYMBOLS = (
|
||||
"Pic_e5b7d", "Pic_e5ddd", "GFX_e6020", "Pic_e6340", "Pic_e6587",
|
||||
"Pic_e67d6", "GFX_e6e6f", "GFX_e718f", "GFX_e74af", "Pic_e77cf",
|
||||
"Pic_f0abf", "Pic_f0cf4",
|
||||
# Yellow-only overworld player surf sprite, loaded outside
|
||||
# SpriteSheetPointerTable (LoadSurfingPlayerSpriteGraphics2) --
|
||||
# needs its own extract like RedBikeSprite. (RFC 0001)
|
||||
"SurfingPikachuSprite",
|
||||
)
|
||||
|
||||
# Yellow-only dialogue whose bank labels carry no leading underscore, so
|
||||
|
||||
@@ -6574,7 +6574,7 @@
|
||||
"battleType": "BATTLE_TYPE_OLD_MAN",
|
||||
"level": 5,
|
||||
"map": "VIRIDIAN_CITY",
|
||||
"species": "WEEDLE",
|
||||
"species": "RATTATA",
|
||||
"text": "ViridianCityOldManText"
|
||||
},
|
||||
"overworldFx": {
|
||||
@@ -19934,7 +19934,13 @@
|
||||
"imageWidth": 16,
|
||||
"label": "GamblerAsleepSprite"
|
||||
}
|
||||
]
|
||||
],
|
||||
"surfPikachu": {
|
||||
"imageBase": "surfing_pikachu",
|
||||
"imageHeight": 96,
|
||||
"imageWidth": 16,
|
||||
"label": "SurfingPikachuSprite"
|
||||
}
|
||||
},
|
||||
"symbols": {
|
||||
"AbraPicBack": [
|
||||
@@ -21369,6 +21375,22 @@
|
||||
28,
|
||||
16822
|
||||
],
|
||||
"FanClubChairPrintText1": [
|
||||
43,
|
||||
22070
|
||||
],
|
||||
"FanClubChairPrintText2": [
|
||||
43,
|
||||
22210
|
||||
],
|
||||
"FanClubChairPrintText3": [
|
||||
43,
|
||||
22243
|
||||
],
|
||||
"FanClubChairPrintText4": [
|
||||
43,
|
||||
22257
|
||||
],
|
||||
"FarfetchdPicBack": [
|
||||
10,
|
||||
28976
|
||||
@@ -25293,6 +25315,18 @@
|
||||
24,
|
||||
22540
|
||||
],
|
||||
"SSAnneKitchenCook7EelsAuBarbecueText": [
|
||||
38,
|
||||
21363
|
||||
],
|
||||
"SSAnneKitchenCook7PrimeBeefSteakText": [
|
||||
38,
|
||||
21414
|
||||
],
|
||||
"SSAnneKitchenCook7SalmonDuSaladText": [
|
||||
38,
|
||||
21298
|
||||
],
|
||||
"SSAnneKitchenCook7Text": [
|
||||
24,
|
||||
22545
|
||||
@@ -26069,10 +26103,18 @@
|
||||
21,
|
||||
25898
|
||||
],
|
||||
"SilphCo9FNurseDontGiveUpText": [
|
||||
38,
|
||||
30668
|
||||
],
|
||||
"SilphCo9FNurseText": [
|
||||
23,
|
||||
22467
|
||||
],
|
||||
"SilphCo9FNurseThankYouText": [
|
||||
38,
|
||||
30683
|
||||
],
|
||||
"SilphCo9FNurseYouLookTiredText": [
|
||||
38,
|
||||
30622
|
||||
@@ -26237,10 +26279,18 @@
|
||||
32,
|
||||
25380
|
||||
],
|
||||
"SurfingPikachuSprite": [
|
||||
63,
|
||||
28143
|
||||
],
|
||||
"SwimmerPic": [
|
||||
19,
|
||||
20787
|
||||
],
|
||||
"TMNotebookText": [
|
||||
39,
|
||||
27310
|
||||
],
|
||||
"TamerPic": [
|
||||
19,
|
||||
23374
|
||||
@@ -26753,6 +26803,10 @@
|
||||
6,
|
||||
21103
|
||||
],
|
||||
"ViridianCityFisherYouCanHaveThisText": [
|
||||
45,
|
||||
18396
|
||||
],
|
||||
"ViridianCityGambler1Text": [
|
||||
6,
|
||||
21055
|
||||
@@ -26801,6 +26855,14 @@
|
||||
6,
|
||||
21043
|
||||
],
|
||||
"ViridianCityYoungster2CaterpieAndWeedleDescriptionText": [
|
||||
45,
|
||||
18125
|
||||
],
|
||||
"ViridianCityYoungster2OkThenText": [
|
||||
45,
|
||||
18111
|
||||
],
|
||||
"ViridianCityYoungster2Text": [
|
||||
6,
|
||||
21067
|
||||
@@ -39174,7 +39236,30 @@
|
||||
]
|
||||
},
|
||||
"labels": [
|
||||
"FanClubChairPrintText1",
|
||||
"FanClubChairPrintText2",
|
||||
"FanClubChairPrintText3",
|
||||
"FanClubChairPrintText4",
|
||||
"MelanieBulbasaurText",
|
||||
"MelanieOddishText",
|
||||
"MelanieSandshrewText",
|
||||
"MelanieText1",
|
||||
"MelanieText2",
|
||||
"MelanieText3",
|
||||
"MelanieText4",
|
||||
"MelanieText5",
|
||||
"SSAnneKitchenCook7EelsAuBarbecueText",
|
||||
"SSAnneKitchenCook7PrimeBeefSteakText",
|
||||
"SSAnneKitchenCook7SalmonDuSaladText",
|
||||
"SilphCo2FSilphWorkerFPleaseTakeThisText",
|
||||
"SilphCo9FNurseDontGiveUpText",
|
||||
"SilphCo9FNurseThankYouText",
|
||||
"SilphCo9FNurseYouLookTiredText",
|
||||
"TMNotebookText",
|
||||
"TeachingHMsText",
|
||||
"ViridianCityFisherYouCanHaveThisText",
|
||||
"ViridianCityYoungster2CaterpieAndWeedleDescriptionText",
|
||||
"ViridianCityYoungster2OkThenText",
|
||||
"_AIBattleUseItemText",
|
||||
"_AIBattleWithdrawText",
|
||||
"_AbandonLearningText",
|
||||
@@ -41860,15 +41945,7 @@
|
||||
"_YeahText",
|
||||
"_YourNameIsText",
|
||||
"_ZapdosDexEntry",
|
||||
"_ZubatDexEntry",
|
||||
"MelanieText1",
|
||||
"MelanieText2",
|
||||
"MelanieText3",
|
||||
"MelanieText4",
|
||||
"MelanieText5",
|
||||
"MelanieBulbasaurText",
|
||||
"MelanieOddishText",
|
||||
"MelanieSandshrewText"
|
||||
"_ZubatDexEntry"
|
||||
],
|
||||
"pointers": {
|
||||
"AgathasRoom": {
|
||||
|
||||
@@ -41,6 +41,10 @@ local S
|
||||
-- vanilla records over an already-merged Data
|
||||
local mods
|
||||
local mouseClicked = false
|
||||
-- Wheel notches queued by App.wheelmoved since the last draw, handed to Kit
|
||||
-- there like mouseClicked is: LOVE delivers events before love.draw, so a
|
||||
-- notch is always spent by the frame that follows it (#595).
|
||||
local wheelY = 0
|
||||
|
||||
-- Which game's cache Data was loaded from. main.lua checks this before
|
||||
-- opening the editor on a save from the other version, because the two
|
||||
@@ -602,7 +606,7 @@ local function drawStatusBar(x, y, w, h)
|
||||
and (ctrl .. "+S save . " .. ctrl ..
|
||||
"+R reload . Esc clear selection . Close returns to the launcher")
|
||||
or (ctrl .. "+S save . " .. ctrl ..
|
||||
"+R reload . Esc clear selection . arrows pan map . wheel zoom")
|
||||
"+R reload . Esc clear selection . arrows pan map . wheel scrolls lists")
|
||||
local hintW = Kit.textWidth("tiny", hint)
|
||||
Kit.textRight("tiny", hint, x + w - pad, y + (h - Kit.textHeight("tiny")) / 2, PAL.faint)
|
||||
local avail = w - 2 * pad - hintW - 14 * s
|
||||
@@ -620,8 +624,9 @@ function App.draw()
|
||||
local s = Kit.scale
|
||||
|
||||
local mx, my = love.mouse.getPosition()
|
||||
Kit.beginFrame(mx, my, mouseClicked)
|
||||
Kit.beginFrame(mx, my, mouseClicked, wheelY)
|
||||
mouseClicked = false
|
||||
wheelY = 0
|
||||
-- Modal shield. Kit has no z-order, so the picker cannot simply be drawn
|
||||
-- last: the chrome and the panel underneath would take the same tap. The
|
||||
-- shield goes up before anything dispatches and comes down only for the
|
||||
@@ -699,9 +704,13 @@ end
|
||||
|
||||
function App.wheelmoved(x, y)
|
||||
if not S then return end
|
||||
-- The map tab spends the wheel on zoom; every other tab routes it through
|
||||
-- Kit so whichever list the pointer is over takes it next draw (#595).
|
||||
if S.tab == "map" and MapBrowser.wheelmoved then
|
||||
MapBrowser.wheelmoved(S, y)
|
||||
return
|
||||
end
|
||||
wheelY = wheelY + (y or 0)
|
||||
end
|
||||
|
||||
function App.quit()
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
-- Immediate-mode widget kit for the save editor, drawn in the launcher's
|
||||
-- visual language (see Theme.lua and SaveEditor.dc.html).
|
||||
--
|
||||
-- Call Kit.beginFrame(mx, my, clicked) once per love.draw() before any widget
|
||||
-- and Kit.endFrame() after the last one; widgets read the frame's mouse state
|
||||
-- to decide hover / click, and endFrame retires the text-input queue so a
|
||||
-- keystroke is never applied twice.
|
||||
-- Call Kit.beginFrame(mx, my, clicked, wheel) once per love.draw() before any
|
||||
-- widget and Kit.endFrame() after the last one; widgets read the frame's mouse
|
||||
-- state to decide hover / click, and endFrame retires the text-input queue so a
|
||||
-- keystroke is never applied twice. The wheel notches accumulated since the
|
||||
-- last frame arrive the same way and are retired the same way: an unclaimed
|
||||
-- notch dies with the frame rather than scrolling something later (#595).
|
||||
--
|
||||
-- Hit testing is a plain rect with no z-order, so panels must draw
|
||||
-- overlapping controls in dispatch order and every target is >= 26px tall
|
||||
@@ -17,6 +19,7 @@ local PAL = Theme.PAL
|
||||
local Kit = {}
|
||||
Kit.mouseX, Kit.mouseY = 0, 0
|
||||
Kit.mouseClicked = false -- left button pressed this frame
|
||||
Kit.wheelY = 0 -- wheel notches queued since the last frame (#595)
|
||||
Kit.focus = nil -- id of the text field receiving keystrokes
|
||||
Kit.time = 0
|
||||
Kit.fonts = {}
|
||||
@@ -58,9 +61,10 @@ local function canPrintf()
|
||||
return G and type(G.printf) == "function"
|
||||
end
|
||||
|
||||
function Kit.beginFrame(mx, my, clicked)
|
||||
function Kit.beginFrame(mx, my, clicked, wheel)
|
||||
Kit.mouseX, Kit.mouseY = mx, my
|
||||
Kit.mouseClicked = clicked
|
||||
Kit.wheelY = wheel or 0
|
||||
if love and love.timer and love.timer.getTime then
|
||||
Kit.time = love.timer.getTime()
|
||||
end
|
||||
@@ -68,8 +72,10 @@ end
|
||||
|
||||
-- Retire this frame's keystrokes. Anything typed while no field had focus is
|
||||
-- dropped here rather than replayed into the next field that gets clicked.
|
||||
-- A wheel notch no list claimed retires with them, for the same reason.
|
||||
function Kit.endFrame()
|
||||
for i = #edits, 1, -1 do edits[i] = nil end
|
||||
Kit.wheelY = 0
|
||||
end
|
||||
|
||||
-- Rebuild the font set when the window size changes. `s` matched the
|
||||
@@ -427,6 +433,29 @@ function Kit.pager(x, y, w, offset, total, perPage)
|
||||
return offset, h
|
||||
end
|
||||
|
||||
-- ----------------------------------------------------------------- scroll
|
||||
-- Mouse wheel over a list body (#595): same offset contract as Kit.pager, so
|
||||
-- a list can carry both and stay on one page counter. Three rules, all of
|
||||
-- them consequences of Kit having no z-order:
|
||||
-- * only the list the pointer is inside takes the notch,
|
||||
-- * the notch is consumed, so two stacked lists cannot both eat it,
|
||||
-- * Kit.blockClicks shields it exactly as it shields Kit.press, or the
|
||||
-- panel under an open species picker would scroll through the modal.
|
||||
local SCROLL_ROWS = 3
|
||||
|
||||
function Kit.scroll(x, y, w, h, offset, total, perPage)
|
||||
local maxOffset = math.max(0, (total or 0) - (perPage or 0))
|
||||
offset = Theme.clamp(offset or 0, 0, maxOffset)
|
||||
if Kit.blockClicks or (Kit.wheelY or 0) == 0 then return offset end
|
||||
if not Kit.hit(x, y, w, h) then return offset end
|
||||
-- LOVE reports wheel-up as positive y; up moves the window toward the top
|
||||
-- of the list, which is a smaller offset.
|
||||
local rows = math.max(1, math.min(SCROLL_ROWS, perPage or SCROLL_ROWS))
|
||||
local step = (Kit.wheelY > 0) and -rows or rows
|
||||
Kit.wheelY = 0
|
||||
return Theme.clamp(offset + step, 0, maxOffset)
|
||||
end
|
||||
|
||||
-- Clip drawing to a rect (list bodies). No-ops under the headless stub.
|
||||
function Kit.pushClip(x, y, w, h)
|
||||
-- A compact mobile viewport can leave a panel with no room for a list.
|
||||
|
||||
@@ -55,6 +55,7 @@ function State.new()
|
||||
selectedItemId = nil,
|
||||
selectedBagId = nil,
|
||||
selectedPcId = nil,
|
||||
itemPickOffset = 0, -- scroll position in the ADD ITEM list (#595)
|
||||
bagOffset = 0,
|
||||
pcOffset = 0,
|
||||
|
||||
|
||||
@@ -4,9 +4,10 @@
|
||||
--
|
||||
-- The picker is a searchable list rather than the old pair of arrows that
|
||||
-- cycled one id at a time through ~250 items, which was the single worst
|
||||
-- interaction in the editor. Badges sit in the wallet column as toggle
|
||||
-- chips because they are boolean inventory flags, not stackable items, and
|
||||
-- must not look like quantity rows.
|
||||
-- interaction in the editor. It scrolls under the mouse wheel too (#595):
|
||||
-- typing used to be the only way to reach an id past the first screenful.
|
||||
-- Badges sit in the wallet column as toggle chips because they are boolean
|
||||
-- inventory flags, not stackable items, and must not look like quantity rows.
|
||||
|
||||
local Bag = require("src.inventory.Bag")
|
||||
local Theme = require("Theme")
|
||||
@@ -97,8 +98,12 @@ function M.draw(S, Kit, x, y, w, h)
|
||||
Kit.card(x, pickY, leftW, pickH)
|
||||
Kit.caption(x + pad, pickY + pad, "ADD ITEM")
|
||||
local qy = pickY + pad + Kit.textHeight("caption") + 8 * s
|
||||
local prevQuery = S.itemQuery or ""
|
||||
S.itemQuery = Kit.textfield("item-query", x + pad, qy, leftW - 2 * pad, 32 * s,
|
||||
S.itemQuery or "", "search item ids...")
|
||||
-- a new query is a new list: keep the first hit on screen rather than
|
||||
-- leaving the view parked wherever the old result set had scrolled to
|
||||
if S.itemQuery ~= prevQuery then S.itemPickOffset = 0 end
|
||||
|
||||
local choices = {}
|
||||
for _, id in ipairs(S.cat.items) do
|
||||
@@ -117,9 +122,14 @@ function M.draw(S, Kit, x, y, w, h)
|
||||
local cRowH = 28 * s
|
||||
local cGap = 5 * s
|
||||
local visible = math.max(1, math.floor((listBottom - listTop) / (cRowH + cGap)))
|
||||
-- #595: the wheel drives the same offset a pager would, so the whole
|
||||
-- catalog is reachable with the mouse alone. Kit.scroll clamps, which is
|
||||
-- also what pulls the view back when a narrower query shortens the list.
|
||||
S.itemPickOffset = Kit.scroll(x + pad, listTop, leftW - 2 * pad,
|
||||
listBottom - listTop, S.itemPickOffset or 0, #choices, visible)
|
||||
Kit.pushClip(x + pad, listTop, leftW - 2 * pad, listBottom - listTop)
|
||||
for i = 1, math.min(visible, #choices) do
|
||||
local id = choices[i]
|
||||
for i = 1, math.min(visible, #choices - S.itemPickOffset) do
|
||||
local id = choices[S.itemPickOffset + i]
|
||||
local ry = listTop + (i - 1) * (cRowH + cGap)
|
||||
if Kit.row(x + pad, ry, leftW - 2 * pad, cRowH, id == S.selectedItemId,
|
||||
PAL.green, 8 * s) then
|
||||
@@ -130,10 +140,11 @@ function M.draw(S, Kit, x, y, w, h)
|
||||
x + pad + 10 * s, ry + (cRowH - Kit.textHeight("mono")) / 2, PAL.text)
|
||||
end
|
||||
Kit.popClip()
|
||||
-- the overflow count rides the caption line, where it can never collide
|
||||
-- the position counter rides the caption line, where it can never collide
|
||||
-- with the list body or the two add buttons below it
|
||||
if #choices > visible then
|
||||
Kit.textRight("micro", ("+%d more"):format(#choices - visible),
|
||||
Kit.textRight("micro", ("%d-%d of %d"):format(S.itemPickOffset + 1,
|
||||
math.min(S.itemPickOffset + visible, #choices), #choices),
|
||||
x + leftW - pad, pickY + pad, PAL.faint)
|
||||
elseif #choices == 0 then
|
||||
Kit.text("mono", "no item matches", x + pad + 10 * s, listTop + 8 * s, PAL.faint)
|
||||
@@ -195,6 +206,9 @@ function M.draw(S, Kit, x, y, w, h)
|
||||
local rowGap = 6 * s
|
||||
local perPage = math.max(1, math.floor((pagerY - 12 * s - rowsTop) / (rowH + rowGap)))
|
||||
S.bagOffset = Ops.clamp(S.bagOffset or 0, 0, math.max(0, #order - perPage))
|
||||
-- the wheel moves the same offset the pager below does (#595)
|
||||
S.bagOffset = Kit.scroll(bagX + pad, rowsTop, listW - 2 * pad,
|
||||
pagerY - 12 * s - rowsTop, S.bagOffset, #order, perPage)
|
||||
|
||||
if #order == 0 then
|
||||
Kit.emptyBox(bagX + pad, rowsTop, listW - 2 * pad, 70 * s, "Bag is empty.")
|
||||
@@ -221,6 +235,8 @@ function M.draw(S, Kit, x, y, w, h)
|
||||
Kit.textRight("mono", ("%d kinds"):format(#pcOrder), pcX + listW - pad,
|
||||
y + pad, PAL.caption)
|
||||
S.pcOffset = Ops.clamp(S.pcOffset or 0, 0, math.max(0, #pcOrder - perPage))
|
||||
S.pcOffset = Kit.scroll(pcX + pad, rowsTop, listW - 2 * pad,
|
||||
pagerY - 12 * s - rowsTop, S.pcOffset, #pcOrder, perPage)
|
||||
if #pcOrder == 0 then
|
||||
Kit.emptyBox(pcX + pad, rowsTop, listW - 2 * pad, 70 * s,
|
||||
"PC storage is empty. Items sent here have no slot cap.")
|
||||
|
||||
Reference in New Issue
Block a user