mirror of
https://github.com/DramaticShape/DramaticShapeVoxelMod.git
synced 2026-08-12 10:10:51 +02:00
add ball throw mechanic
This commit is contained in:
@@ -0,0 +1,98 @@
|
|||||||
|
-- LET'S GO: the whole party's experience, on one card.
|
||||||
|
--
|
||||||
|
-- Sharing experience to everybody has a cost the original game never had
|
||||||
|
-- to pay: six Pokemon means six "X gained N EXP. Points!" boxes for every
|
||||||
|
-- knockout, each needing its own press. That is the same information the
|
||||||
|
-- player wanted, delivered in the most tiring possible way -- and it is
|
||||||
|
-- worse than a wall of text, because the numbers arrive one at a time so
|
||||||
|
-- the one thing a shared payout is FOR (comparing them: the little one
|
||||||
|
-- gained five times what the big one did) can never be seen at once.
|
||||||
|
--
|
||||||
|
-- So the per-Pokemon lines are suppressed and this card is shown in their
|
||||||
|
-- place: one box, one press, every gain side by side, with a level-up
|
||||||
|
-- called out on the row it happened to. What the card cannot cover still
|
||||||
|
-- plays as it always did -- "X grew to level 6!", the stats window, and
|
||||||
|
-- any move learned -- because those are events, not a tally.
|
||||||
|
--
|
||||||
|
-- Drawn with the engine's own font and box, in the GB's own frame, so it
|
||||||
|
-- sits in a staged 3D battle exactly like every other battle panel.
|
||||||
|
|
||||||
|
-- the mod namespace (see main.lua): V.require loads a sibling module
|
||||||
|
local V = ...
|
||||||
|
|
||||||
|
local ExpPanel = {}
|
||||||
|
ExpPanel.__index = ExpPanel
|
||||||
|
|
||||||
|
local Font = nil
|
||||||
|
local function font()
|
||||||
|
if Font ~= nil then return Font or nil end
|
||||||
|
local ok, F = pcall(require, "src.render.Font")
|
||||||
|
Font = ok and F or false
|
||||||
|
return Font or nil
|
||||||
|
end
|
||||||
|
|
||||||
|
-- the GB frame, in 8-pixel tiles
|
||||||
|
local TILE = 8
|
||||||
|
local COLS, ROWS = 20, 18
|
||||||
|
local ROW_H = 12 -- pixels between listed Pokemon
|
||||||
|
|
||||||
|
-- `rows` is filled by the award loop and read HERE, at draw time: the
|
||||||
|
-- loop runs to completion long before the battle queue reaches this
|
||||||
|
-- panel, so the table is always complete by the time it is shown.
|
||||||
|
function ExpPanel.new(game, rows)
|
||||||
|
return setmetatable({ game = game, rows = rows or {}, t = 0 }, ExpPanel)
|
||||||
|
end
|
||||||
|
|
||||||
|
function ExpPanel:update(dt)
|
||||||
|
self.t = (self.t or 0) + (dt or 0)
|
||||||
|
local input = self.game and self.game.input
|
||||||
|
if not input then return end
|
||||||
|
-- a beat of deafness: the press that dismissed whatever came before
|
||||||
|
-- must not dismiss this card in the same breath
|
||||||
|
if self.t < 0.12 then return end
|
||||||
|
if input:wasPressed("a") or input:wasPressed("b") then
|
||||||
|
self.game.stack:pop()
|
||||||
|
if self.onDone then self.onDone() end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
function ExpPanel:draw()
|
||||||
|
local F = font()
|
||||||
|
if not F then return end
|
||||||
|
local rows = self.rows or {}
|
||||||
|
local n = #rows
|
||||||
|
if n == 0 then return end
|
||||||
|
|
||||||
|
-- bottom-anchored and only as tall as it needs to be, so a two-Pokemon
|
||||||
|
-- party does not black out the fight behind it
|
||||||
|
local th = 3 + math.ceil(n * ROW_H / TILE)
|
||||||
|
th = math.min(th, ROWS - 1)
|
||||||
|
local ty = ROWS - th
|
||||||
|
F.drawBox(0, ty, COLS, th)
|
||||||
|
|
||||||
|
love.graphics.setColor(0, 0, 0, 1)
|
||||||
|
local x0 = TILE
|
||||||
|
local y0 = (ty + 1) * TILE + 2
|
||||||
|
F.draw("EXP GAINED", x0, y0)
|
||||||
|
|
||||||
|
for i, r in ipairs(rows) do
|
||||||
|
local y = y0 + ROW_H + (i - 1) * ROW_H
|
||||||
|
if y > (ROWS - 1) * TILE then break end
|
||||||
|
local mon = r.mon
|
||||||
|
local name = mon.nickname
|
||||||
|
or (self.game.data.pokemon[mon.species] or {}).name
|
||||||
|
or tostring(mon.species)
|
||||||
|
F.draw(name, x0, y)
|
||||||
|
-- a level-up is called out where it happened rather than left to the
|
||||||
|
-- message that follows, so the card reads as the whole story
|
||||||
|
if (r.to or 0) > (r.from or 0) then
|
||||||
|
local up = ("L%d"):format(r.to)
|
||||||
|
F.draw(up, 108 - F.width(up), y)
|
||||||
|
end
|
||||||
|
local amt = ("+%d"):format(r.gained or 0)
|
||||||
|
F.draw(amt, 152 - F.width(amt), y)
|
||||||
|
end
|
||||||
|
love.graphics.setColor(1, 1, 1, 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
return ExpPanel
|
||||||
+24
-2
@@ -324,11 +324,33 @@ function LetsGo.install()
|
|||||||
--
|
--
|
||||||
-- Everything else -- CATCH ONLY, the row switched off, another mod's
|
-- Everything else -- CATCH ONLY, the row switched off, another mod's
|
||||||
-- battle -- falls through to the engine's own split untouched.
|
-- battle -- falls through to the engine's own split untouched.
|
||||||
|
-- ------- and it is announced ONCE, not once per Pokemon
|
||||||
|
--
|
||||||
|
-- Six party members would otherwise mean six "X gained N EXP. Points!"
|
||||||
|
-- boxes per knockout. The per-Pokemon lines are suppressed (the `false`
|
||||||
|
-- to applyShare) and one card is shown instead -- see lib/ExpPanel.lua
|
||||||
|
-- for why that is better than a faster wall of the same text.
|
||||||
|
--
|
||||||
|
-- The card is queued BEFORE the loop that fills it. That is not a race:
|
||||||
|
-- applyShare applies its experience immediately and only QUEUES its
|
||||||
|
-- messages, so the loop runs to completion synchronously here, while
|
||||||
|
-- the queue does not reach the card's factory until later -- by which
|
||||||
|
-- time `rows` is complete. Queueing it first is what puts the tally
|
||||||
|
-- ahead of the "grew to level" chatter it is a summary of.
|
||||||
|
local ExpPanel = V.require("ExpPanel")
|
||||||
local function payParty(ctx, mult)
|
local function payParty(ctx, mult)
|
||||||
|
local battle = ctx.battle
|
||||||
|
local rows = {}
|
||||||
|
battle:uiNext(function() return ExpPanel.new(battle.game, rows) end)
|
||||||
granting = { mult = mult }
|
granting = { mult = mult }
|
||||||
local okAward, err = pcall(function()
|
local okAward, err = pcall(function()
|
||||||
for _, mon in ipairs(ctx.battle.game.save.party) do
|
for _, mon in ipairs(battle.game.save.party) do
|
||||||
if mon.hp > 0 then ctx.applyShare(mon, 1, true) end
|
if mon.hp > 0 then
|
||||||
|
local exp0, lv0 = mon.exp, mon.level
|
||||||
|
ctx.applyShare(mon, 1, false)
|
||||||
|
rows[#rows + 1] = { mon = mon, gained = mon.exp - exp0,
|
||||||
|
from = lv0, to = mon.level }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
granting = nil
|
granting = nil
|
||||||
|
|||||||
@@ -27,6 +27,27 @@ return function(game)
|
|||||||
LetsGo.setting:setValue(MODE == "off" and false or MODE, game)
|
LetsGo.setting:setValue(MODE == "off" and false or MODE, game)
|
||||||
U.log("LET'S GO mode: " .. tostring(LetsGo.mode()))
|
U.log("LET'S GO mode: " .. tostring(LetsGo.mode()))
|
||||||
|
|
||||||
|
-- count the summary cards at their SOURCE rather than by catching them
|
||||||
|
-- on the stack: the driver taps fast enough to dismiss one between two
|
||||||
|
-- polls, and an absence there would look like a card that never came
|
||||||
|
local ExpPanel = lib.require("ExpPanel")
|
||||||
|
local innerNew, cards = ExpPanel.new, 0
|
||||||
|
ExpPanel.new = function(g, rows)
|
||||||
|
local panel = innerNew(g, rows)
|
||||||
|
cards = cards + 1
|
||||||
|
-- read at DRAW time in the real thing; here the loop that fills it
|
||||||
|
-- has already run, so peeking now is honest
|
||||||
|
local parts = {}
|
||||||
|
for _, r in ipairs(rows or {}) do
|
||||||
|
parts[#parts + 1] = ("%s+%d%s"):format(
|
||||||
|
(g.data.pokemon[r.mon.species] or {}).name or "?", r.gained or 0,
|
||||||
|
(r.to or 0) > (r.from or 0) and ("->L" .. r.to) or "")
|
||||||
|
end
|
||||||
|
U.log(("EXP CARD #%d: %s"):format(cards, table.concat(parts, " ")))
|
||||||
|
CARD_HOLD = 20 -- frames to stop tapping, so it can be shot
|
||||||
|
return panel
|
||||||
|
end
|
||||||
|
|
||||||
-- one strong fighter and two bystanders: only the fighter gains on the
|
-- one strong fighter and two bystanders: only the fighter gains on the
|
||||||
-- engine's own rules, so the bystanders ARE the test
|
-- engine's own rules, so the bystanders ARE the test
|
||||||
game.save.party = {
|
game.save.party = {
|
||||||
@@ -82,10 +103,28 @@ return function(game)
|
|||||||
-- through the intro to the menu, then FIGHT + first move, over and
|
-- through the intro to the menu, then FIGHT + first move, over and
|
||||||
-- over until the fight resolves. The enemy's HP is logged as it goes,
|
-- over until the fight resolves. The enemy's HP is logged as it goes,
|
||||||
-- so a fight that stalls is visibly a stall rather than a silent zero.
|
-- so a fight that stalls is visibly a stall rather than a silent zero.
|
||||||
local lastHP = nil
|
local lastHP, shotPanel = nil, false
|
||||||
for i = 1, 900 do
|
for i = 1, 900 do
|
||||||
if battle.result then break end
|
-- the fight ending does NOT end the loop until the card has been
|
||||||
if battle.phase == "menu" then
|
-- photographed: the last knockout resolves the battle in the same
|
||||||
|
-- breath that queues the card, so breaking on `result` alone leaves
|
||||||
|
-- every run with the card built and never seen
|
||||||
|
if battle.result and shotPanel then break end
|
||||||
|
-- hold the taps while the card is up, so it can be shot rather than
|
||||||
|
-- dismissed on the next frame
|
||||||
|
if (CARD_HOLD or 0) > 0 then
|
||||||
|
CARD_HOLD = CARD_HOLD - 1
|
||||||
|
if not shotPanel then
|
||||||
|
local top = game.stack and game.stack:top()
|
||||||
|
if top and rawget(top, "rows") then
|
||||||
|
shotPanel = true
|
||||||
|
U.shot(game, (os.getenv("SHOT_DIR") or ".scratchpad")
|
||||||
|
.. "/exp_panel.png")
|
||||||
|
U.log("shot the card")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
U.wait(1)
|
||||||
|
elseif battle.phase == "menu" then
|
||||||
battle.menuIndex = 1 -- FIGHT
|
battle.menuIndex = 1 -- FIGHT
|
||||||
U.tap(game, "a")
|
U.tap(game, "a")
|
||||||
elseif battle.phase == "moveSelect" then
|
elseif battle.phase == "moveSelect" then
|
||||||
@@ -100,6 +139,15 @@ return function(game)
|
|||||||
U.log((" foe HP -> %s (phase %s, step %d)")
|
U.log((" foe HP -> %s (phase %s, step %d)")
|
||||||
:format(tostring(hp), tostring(battle.phase), i))
|
:format(tostring(hp), tostring(battle.phase), i))
|
||||||
end
|
end
|
||||||
|
-- photograph the summary card the moment it is on top, then let the
|
||||||
|
-- taps carry on and dismiss it
|
||||||
|
local top = game.stack and game.stack:top()
|
||||||
|
if top and not shotPanel and top ~= battle and rawget(top, "rows") then
|
||||||
|
shotPanel = true
|
||||||
|
U.shot(game, (os.getenv("SHOT_DIR") or ".scratchpad")
|
||||||
|
.. "/exp_panel.png")
|
||||||
|
U.log("shot the card")
|
||||||
|
end
|
||||||
U.wait(5)
|
U.wait(5)
|
||||||
end
|
end
|
||||||
U.log("battle result: " .. tostring(battle.result)
|
U.log("battle result: " .. tostring(battle.result)
|
||||||
|
|||||||
Reference in New Issue
Block a user