mirror of
https://github.com/bryanthaboi/gen1recomp.git
synced 2026-08-19 04:06:10 +02:00
G2 support
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
-- The in-game trades (engine/events/npc_trade.asm, data/events/npc_trades.asm).
|
||||
--
|
||||
-- Six of them, one per NPC_TRADE_* constant, reached by the `trade` script
|
||||
-- command. Each row names the mon the NPC wants, the mon it hands over, and
|
||||
-- everything that mon arrives wearing: its nickname, its DVs, its held item,
|
||||
-- its original trainer's name and ID, and which gender of the requested mon it
|
||||
-- will accept.
|
||||
--
|
||||
-- love-free: the conversation is src/ui/gen2/TradeMenu.lua, this is the rules.
|
||||
--
|
||||
-- Facts worth keeping:
|
||||
--
|
||||
-- * NPCTRADE_GIVEMON is what YOU hand over and NPCTRADE_GETMON what you get,
|
||||
-- which is the opposite way round from the macro's own argument comment
|
||||
-- ("requested mon, offered mon"). GetTradeAttr reads them by name, so the
|
||||
-- comment is the only thing that is backwards.
|
||||
-- * The row's DVs are TWO RAW BYTES, not a number: attack/defense in the
|
||||
-- high and low nibbles of the first, speed/special of the second. The
|
||||
-- mon's gender and shininess fall straight out of them, which is why every
|
||||
-- one of these trades hands over the same mon to every player.
|
||||
-- * The OT ID is stored little-endian in the table and byte-swapped into the
|
||||
-- party struct (Trade_CopyTwoBytesReverseEndian), so the number the table
|
||||
-- holds IS the ID the player sees.
|
||||
-- * `trade` writes no wScriptVar. Every outcome -- the refusal, the wrong
|
||||
-- mon, the completed trade -- prints its line and returns, and the script
|
||||
-- after it carries on either way.
|
||||
-- * The trade is one-shot, tracked in wTradeFlags by the trade's own id. A
|
||||
-- second visit prints TRADE_DIALOG_AFTER and nothing else, which is the
|
||||
-- check that happens BEFORE the intro line.
|
||||
-- * ComputeNPCTrademonStats runs at the END, on the mon that just landed in
|
||||
-- the last party slot: the received mon keeps the LEVEL of the one handed
|
||||
-- over and recomputes its stats from the new species' bases.
|
||||
|
||||
local Mail = require("src.core.gen2.Mail")
|
||||
local Mon = require("src.battle.gen2.Mon")
|
||||
|
||||
local NpcTrade = {}
|
||||
|
||||
-- constants/npc_trade_constants.asm
|
||||
NpcTrade.NUM_NPC_TRADES = 6
|
||||
NpcTrade.TRADE_GENDER_EITHER = "TRADE_GENDER_EITHER"
|
||||
NpcTrade.TRADE_GENDER_MALE = "TRADE_GENDER_MALE"
|
||||
NpcTrade.TRADE_GENDER_FEMALE = "TRADE_GENDER_FEMALE"
|
||||
|
||||
-- The outcomes, which are also the TRADE_DIALOG_* rows PrintTradeText picks.
|
||||
NpcTrade.DIALOG_INTRO = "TRADE_DIALOG_INTRO"
|
||||
NpcTrade.DIALOG_CANCEL = "TRADE_DIALOG_CANCEL"
|
||||
NpcTrade.DIALOG_WRONG = "TRADE_DIALOG_WRONG"
|
||||
NpcTrade.DIALOG_COMPLETE = "TRADE_DIALOG_COMPLETE"
|
||||
NpcTrade.DIALOG_AFTER = "TRADE_DIALOG_AFTER"
|
||||
|
||||
-- data/generated/events.lua `trades`, 1-based over the 0-based NPC_TRADE_*.
|
||||
function NpcTrade.row(eventTables, id)
|
||||
local rows = type(eventTables) == "table" and eventTables.trades
|
||||
if type(rows) ~= "table" then return nil end
|
||||
return rows[(tonumber(id) or 0) + 1]
|
||||
end
|
||||
|
||||
-- wTradeFlags, a bit per trade id. Save-side it is a plain set.
|
||||
function NpcTrade.done(save, id)
|
||||
local flags = save and save.tradeFlags
|
||||
return (flags and flags[tonumber(id) or -1]) == true
|
||||
end
|
||||
|
||||
function NpcTrade.markDone(save, id)
|
||||
if not save then return end
|
||||
save.tradeFlags = save.tradeFlags or {}
|
||||
save.tradeFlags[tonumber(id) or 0] = true
|
||||
end
|
||||
|
||||
-- The row's two DV bytes as the port's named-DV table. `dn attack, defense`
|
||||
-- then `dn speed, special` -- the same packing wild mons use.
|
||||
function NpcTrade.dvs(row)
|
||||
local raw = (row and row.dvs) or {}
|
||||
local dvs = {
|
||||
attack = math.floor((raw[1] or 0) / 16),
|
||||
defense = (raw[1] or 0) % 16,
|
||||
speed = math.floor((raw[2] or 0) / 16),
|
||||
special = (raw[2] or 0) % 16,
|
||||
}
|
||||
dvs.hp = Mon.hpDV(dvs)
|
||||
return dvs
|
||||
end
|
||||
|
||||
-- NPCTRADE_ITEM is an item id BYTE (data/events/npc_trades.asm's `db \5, \6,
|
||||
-- \7` tail), and DoNPCTrade copies that byte straight into wPartyMon1Item of
|
||||
-- the last party slot, so the received mon wears it like any other held item.
|
||||
-- Everywhere else in this port a held item is a KEY of data/generated/items.lua
|
||||
-- -- wild base data, trainer party mons and `givepokemail` are named at
|
||||
-- extraction, and `givepoke` names its own byte at runtime through World's
|
||||
-- itemByIndex -- so the byte is named here too and nothing downstream has to
|
||||
-- know the row is raw. A row item of 0 is NO_ITEM. A cache that already
|
||||
-- carries the name passes straight through.
|
||||
function NpcTrade.item(data, row)
|
||||
local raw = row and row.item
|
||||
if raw == nil or raw == 0 then return nil end
|
||||
if type(raw) == "string" then return raw end
|
||||
local items = data and data.items
|
||||
if type(items) == "table" then
|
||||
for id, def in pairs(items) do
|
||||
if type(def) == "table" and def.index == raw then return id end
|
||||
end
|
||||
end
|
||||
local order = data and data.constants and data.constants.itemOrder
|
||||
return (order and order[raw]) or nil
|
||||
end
|
||||
|
||||
-- CheckTradeGender. EITHER takes anything; the other two run GetGender on the
|
||||
-- mon the player picked and refuse on a mismatch. A genderless species
|
||||
-- ("unknown") satisfies neither, which is the `jr nz` / `jr z` pair falling to
|
||||
-- .not_matching.
|
||||
function NpcTrade.genderOk(row, mon)
|
||||
local want = row and row.gender
|
||||
if not want or want == NpcTrade.TRADE_GENDER_EITHER then return true end
|
||||
local gender = mon and mon.gender
|
||||
if want == NpcTrade.TRADE_GENDER_MALE then return gender == "male" end
|
||||
return gender == "female"
|
||||
end
|
||||
|
||||
-- The three refusals NPCTrade checks in order, before any swap happens.
|
||||
-- Answers the TRADE_DIALOG_* the conversation should print, or nil for "go
|
||||
-- ahead".
|
||||
function NpcTrade.check(row, mon)
|
||||
if not row then return NpcTrade.DIALOG_CANCEL end
|
||||
if not mon then return NpcTrade.DIALOG_CANCEL end
|
||||
if mon.species ~= row.give then return NpcTrade.DIALOG_WRONG end
|
||||
if not NpcTrade.genderOk(row, mon) then return NpcTrade.DIALOG_WRONG end
|
||||
return nil
|
||||
end
|
||||
|
||||
-- DoNPCTrade: the mon at `index` leaves the party and the row's mon takes the
|
||||
-- last slot, at the SAME level, with the row's DVs, nickname, held item, OT
|
||||
-- name and OT ID. Answers the two mons, given away first.
|
||||
--
|
||||
-- RemoveMonFromPartyOrBox runs before TryAddMonToParty, so the incoming mon
|
||||
-- lands in the slot vacated by the outgoing one only when that was the last
|
||||
-- slot -- otherwise the party closes up and the new mon goes on the end. That
|
||||
-- reordering is visible in the party list, so it is reproduced rather than
|
||||
-- tidied into an in-place swap.
|
||||
function NpcTrade.perform(data, save, row, index)
|
||||
local party = save and save.party
|
||||
local given = party and party[index]
|
||||
if not (data and given and row) then return nil end
|
||||
local received = Mon.new(data, row.get, given.level, {
|
||||
dvs = NpcTrade.dvs(row),
|
||||
nickname = row.nickname,
|
||||
item = NpcTrade.item(data, row),
|
||||
})
|
||||
if not received then return nil end
|
||||
-- `ot` is what Breeding reads and `otName` what the summary screen prints;
|
||||
-- both are set rather than picking one, because the two halves of the port
|
||||
-- already disagree and a traded mon has to answer both.
|
||||
received.ot, received.otName = row.otName, row.otName
|
||||
received.otId = row.otId
|
||||
table.remove(party, index)
|
||||
-- RemoveMonFromPartyOrBox's "Mail time!" tail. NPCTrade itself has no mail
|
||||
-- check -- unlike the Day-Care and the PC, it will trade a mon holding a
|
||||
-- letter away -- so the shift here is what stops the mon that closes up into
|
||||
-- that slot inheriting it (src/core/gen2/Mail.lua).
|
||||
Mail.removeSlot(save, index)
|
||||
party[#party + 1] = received
|
||||
return given, received
|
||||
end
|
||||
|
||||
return NpcTrade
|
||||
Reference in New Issue
Block a user