diff --git a/data/scripts/story4.lua b/data/scripts/story4.lua index 598831ff..874a2b9c 100644 --- a/data/scripts/story4.lua +++ b/data/scripts/story4.lua @@ -171,6 +171,28 @@ local function dojoBall(species, ownBall, otherBall, askKey) end end +-- FightingDojoDefaultScript does not use the Master's facing direction for +-- this battle. It checks exactly the cell left of him, turns both sprites +-- toward one another, and then displays his trainer text. Keeping this out +-- of trainer sight also lets him keep his original downward-facing pose. +local function dojoMasterGate(game, ow, x, y) + if x ~= 4 or y ~= 3 or game.save.flags.EVENT_BEAT_KARATE_MASTER then + return false + end + local master + for _, npc in ipairs(ow.npcs) do + if npc.def and npc.def.name == "FIGHTINGDOJO_KARATE_MASTER" then + master = npc + break + end + end + if not master or ow:trainerDefeated(master) then return false end + ow.player.facing = "right" + master:facePlayer(ow.player) + ow:engageTrainer(master) + return true +end + M.FIGHTING_DOJO = { talk = { TEXT_FIGHTINGDOJO_HITMONLEE_POKE_BALL = @@ -197,6 +219,7 @@ M.FIGHTING_DOJO = { end return false end, + onStep = dojoMasterGate, } -- ------------------------------------------------------------------- diff --git a/src/core/Data.lua b/src/core/Data.lua index a68c050d..ae292d40 100644 --- a/src/core/Data.lua +++ b/src/core/Data.lua @@ -144,8 +144,8 @@ end -- no def_trainers row (DisplayTextID routes to his ASM script), so the -- extractor emits headers only for the four blackbelts ([2]..[5]). Seed -- object index [1] so he behaves like the real leader: --- * range 4 (matches the strongest blackbelt) -> CheckFightingMapTrainers --- spots the player in his DOWN line and he challenges on sight (#197), +-- * range 0: FightingDojoDefaultScript, not trainer sight, starts his +-- battle from the single tile to his left (#495), -- * battle = his pre-battle challenge, won = "Hwa! Arrgh! Beaten!", -- * after = the "Stay and train at Karate with us!" re-talk line. -- Deliberately NO `event`: EVENT_BEAT_KARATE_MASTER is owned by @@ -160,7 +160,7 @@ function Data:seedFightingDojoKarateMaster() headers.FightingDojo = headers.FightingDojo or {} if headers.FightingDojo[1] then return end headers.FightingDojo[1] = { - range = 4, + range = 0, battle = "_FightingDojoKarateMasterText", won = "_FightingDojoKarateMasterDefeatedText", after = "_FightingDojoKarateMasterStayAndTrainWithUsText", diff --git a/tests/drivers/fighting_dojo_bug197_test.lua b/tests/drivers/fighting_dojo_bug197_test.lua index 2fed34aa..ce529e1d 100644 --- a/tests/drivers/fighting_dojo_bug197_test.lua +++ b/tests/drivers/fighting_dojo_bug197_test.lua @@ -1,6 +1,6 @@ -- Driver: Fighting Dojo Karate Master bundle (#197). -- Six sub-bugs live in FIGHTING_DOJO (scripts/FightingDojo.asm): --- BUG1 no aggro -- the master has no trainer header so range=0 +-- BUG1 gate -- the master stops the player on the tile to his left -- BUG2 no speech -- no won text + no prize dialogue after the win -- BUG3 wrong re-talk -- shows the pre-battle challenge, not the after line -- BUG4 (verify) -- the ball ask() is the Gen1 descriptor, not a dex entry @@ -112,21 +112,23 @@ return function(game) ------------------------------------------------------------------ local hdr = game.data:trainerHeader("FightingDojo", 1) check(hdr ~= nil, "BUG1/2/3: Karate Master trainer header (index 1) exists") - check(hdr and (hdr.range or 0) > 0, "BUG1: master has a sight range") + check(hdr and (hdr.range or 0) == 0, + "BUG1: master relies on the exact-tile gate, not trainer sight") check(hdr and hdr.won ~= nil, "BUG2: master has a won (defeat) text") check(hdr and hdr.after ~= nil, "BUG3: master has an after (re-talk) text") ------------------------------------------------------------------ - -- BUG1: sight aggro. Stand directly below the master (5,3 faces DOWN, - -- range 4) with the four blackbelts pre-cleared so only he can engage. + -- BUG1: exact gate. Start just north of the tile to the Master's left, + -- then step down once. The four blackbelts are cleared so only he reacts. ------------------------------------------------------------------ - local ow = resetDojo(5, 4, "up", {}) + local ow = resetDojo(4, 2, "down", {}) U.shot(game, DIR .. "/dojo_1_before.png") - U.wait(20) -- the idle sight scan runs every frame - local engaged = ow.engaging or (ow.emote ~= nil) - U.log("aggro engaging:", tostring(ow.engaging), "emote:", tostring(ow.emote ~= nil)) + U.tap(game, "down") + U.wait(30) + local engaged = topIsTextBox() + U.log("gate dialogue open:", tostring(engaged)) U.shot(game, DIR .. "/dojo_2_aggro.png") - check(engaged, "BUG1: Karate Master aggros on sight") + check(engaged, "BUG1: Karate Master stops the player at his left") ------------------------------------------------------------------ -- BUG3: talk to the already-beaten master -> "Stay and train..." and diff --git a/tests/drivers/fighting_dojo_gate_bug495_test.lua b/tests/drivers/fighting_dojo_gate_bug495_test.lua new file mode 100644 index 00000000..9f245bd4 --- /dev/null +++ b/tests/drivers/fighting_dojo_gate_bug495_test.lua @@ -0,0 +1,44 @@ +-- Driver: Fighting Dojo Karate Master gate (#495). +-- +-- Run: +-- POKEPORT_DRIVER=tests/drivers/fighting_dojo_gate_bug495_test.lua \ +-- POKEPORT_TOUCH=0 POKEPORT_VERSION=yellow love . +-- +-- The scene starts one tile north of the only trigger tile. The reported +-- behaviour is an interaction timing and position question for a human to +-- observe, not an assertion this driver can decide. +return function(game) + local U = dofile("tests/drivers/util.lua") + + local function check(label, ok) + U.log(ok and "PASS" or "FAIL", label) + return ok + end + + U.teleport(game, "FIGHTING_DOJO", 4, 2, "down") + local ow = game.stack:top() + local master + for _, npc in ipairs(ow.npcs) do + if npc.def and npc.def.name == "FIGHTINGDOJO_KARATE_MASTER" then + master = npc + break + end + end + + -- Fixture checks only: the room, player, and Master must be in the layout + -- that makes the reported tile meaningful. + check("Fighting Dojo is loaded", ow.map.id == "FIGHTING_DOJO") + check("player starts north of the trigger tile", + ow.player.cellX == 4 and ow.player.cellY == 2) + check("Karate Master is at (5,3)", + master and master.cellX == 5 and master.cellY == 3) + + U.log("Issue #495: Karate Master gate") + U.log("Do this: press Down once, onto the tile left of the Master.") + U.log("Right: he turns left and starts his challenge on that tile only.") + U.log("Wrong: nothing happens here, while standing below him triggers a battle.") + + while true do + coroutine.yield() + end +end diff --git a/tests/parity_fighting_dojo_gate.lua b/tests/parity_fighting_dojo_gate.lua new file mode 100644 index 00000000..52c32564 --- /dev/null +++ b/tests/parity_fighting_dojo_gate.lua @@ -0,0 +1,58 @@ +-- FightingDojoDefaultScript gates the Karate Master at the one tile to his +-- left. He is not a regular sight-line trainer (#495). + +package.path = "./?.lua;./?/init.lua;" .. package.path +if not _G.love then _G.love = require("tests.love_stub") end + +local Data = require("src.core.Data") +if not Data.maps then Data:load() end + +local S = require("tests.harness").suite("parity Fighting Dojo gate") +local check, eq = S.check, S.eq +local script = require("data.scripts.story4").FIGHTING_DOJO.onStep + +local function scene() + local master = { + def = { name = "FIGHTINGDOJO_KARATE_MASTER" }, + facing = "down", + facePlayer = function(self, player) + self.facing = player.cellX < 5 and "left" or "right" + end, + } + local ow = { + npcs = { master }, + player = { cellX = 4, cellY = 3, facing = "down" }, + trainerDefeated = function() return false end, + engageTrainer = function(self, npc) self.engaged = npc end, + } + return { save = { flags = {} } }, ow, master +end + +local header = Data:trainerHeader("FightingDojo", 1) +eq(header and header.range, 0, + "Karate Master has no generic trainer sight range") + +do + local game, ow = scene() + check(not script(game, ow, 5, 4), + "standing below the downward-facing Master does not trigger him") + check(ow.engaged == nil, "the below tile does not begin a battle") +end + +do + local game, ow, master = scene() + check(script(game, ow, 4, 3), + "the tile immediately left of the Master triggers his script") + eq(ow.engaged, master, "the Master starts the battle") + eq(master.facing, "left", "the Master turns toward the player") + eq(ow.player.facing, "right", "the player turns toward the Master") +end + +do + local game, ow = scene() + game.save.flags.EVENT_BEAT_KARATE_MASTER = true + check(not script(game, ow, 4, 3), + "the gate stays inactive after the Karate Master is beaten") +end + +S.finish()