· 5 years ago · Apr 16, 2020, 06:56 AM
1
2
3
4
5
6
7--[=[
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 this space intentionally left blank
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39--]=]
40
41-- ====================
42-- SAVE GAME SYSTEM
43-- ====================
44
45function doSave(slot)
46 local slotNames = { [901] = "in-game 1", [902] = "in-game 2", [903] = "in-game 3", [999] = "autosave" }
47 local Persistent = {}
48
49 -- not particularly important, just used to prevent re-checking frequently called functions
50 -- functions cannot be saved/loaded to file though hence the clearing beforehand
51
52 for i,v in pairs({ BattleState = BattleState,
53 MenuState = MenuState,
54 GameState = GameState, }) do
55 Persistent[i] = v
56 end
57 print(os.date("[%H:%M:%S] ").."Saved script data in slot "..(slotNames[slot] or slot).."!")
58 return { Persistent = Persistent }
59end
60function doLoad(slot)
61 BattleState = {}
62 MenuState = {}
63 GameState = {}
64
65 local currentGameSlot = memory.readbyte(0x7e0224)
66 -- don't do anything if this is before a saved game is loaded
67 -- (don't yell at the player and don't bother filling in anything)
68 if (currentGameSlot == 0x00 or currentGameSlot == 0x55) then
69 return
70 end
71 local data = savestate.loadscriptdata(slot)
72 local slotNames = { [901] = "in-game 1", [902] = "in-game 2", [903] = "in-game 3", [999] = "autosave" }
73 if (data and data.Persistent) then
74 local Persistent = data.Persistent
75 for i,v in pairs(Persistent) do
76 _G[i] = v
77 end
78 if (slot == 999) then
79 local fr = Persistent.GameState and Persistent.GameState.__CloseScriptFrame
80 local now = emu.framecount()
81 if (fr) then
82 if ((fr < now - 2) or (fr > now + 2)) then
83 -- gui.popup("Note that loading this script while a save game is loaded may result in loss of progress, if you have played since you last exited the script.\n\nPlease either load a save state now, or reset the game (without saving) and load a save game through the menu in order to properly load your progress.\n\nYour data is automatically saved whenever you create a save state or save your game using the in-game menu; a save state is also created whenever you hard save (901-903 in your saves folder).\n\nIf you are seeing this message before a game has been loaded, or because you stopped the script and immediately restarted it, feel free to ignore it.")
84 end
85 GameState.__CloseScriptFrame = nil
86 end
87 end
88
89 local s = slot
90
91 print(os.date("[%H:%M:%S] ").."Loaded script data in slot "..(slotNames[slot] or slot).."!")
92 else
93 print(os.date("[%H:%M:%S] ").."Failed to load script data in slot "..(slotNames[slot] or slot).."!")
94 end
95end
96function closeScriptSave()
97 GameState.__CloseScriptFrame = emu.framecount()
98 local ss = savestate.create(999)
99 savestate.save(ss)
100end
101savestate.registersave(doSave)
102savestate.registerload(doLoad)
103local savingState = false
104memory.registerexec(0xc31526,function() savingState = true end) -- saving game in menu; saves state on next frame
105memory.registerexec(0xc329eb,function() doLoad(900 + memory.readbyte(0x7e0224)) end) -- load game
106memory.registerexec(0xc301a7,function() GameState = {} end) -- new game
107-- Put any data you want saved into these variables.
108-- BattleState is wiped if not in a fight.
109-- MenuState is wiped if not in the menu.
110-- GameState is persistent and should contain e.g. learn progress.
111
112BattleState = {}
113BattleState.Effects = {}
114MenuState = {}
115GameState = {}
116
117-- frequently deleted. do not put anything important here, just use it for storing data
118-- we won't necessarily want to call over and over
119BattleState.Cache = {}
120MenuState.Cache = {}
121GameState.Cache = {}
122
123emu.registerexit(closeScriptSave) -- automatically saves state to slot 999 when exiting lua
124doLoad(999) -- automatically loads script data from state 999 when starting
125
126-- ====================
127-- END SAVE GAME SYSTEM
128-- ====================
129
130function configSpells()
131 SPELL_FIRE = 0 SPELL_ICE = 1 SPELL_BOLT = 2 SPELL_POISON = 3 SPELL_DRAIN = 4
132 SPELL_FIRE2 = 5 SPELL_ICE2 = 6 SPELL_BOLT2 = 7 SPELL_BIO = 8
133 SPELL_FIRE3 = 9 SPELL_ICE3 = 10 SPELL_BOLT3 = 11 SPELL_BREAK = 12 SPELL_DOOM = 13
134 SPELL_PEARL = 14 SPELL_FLARE = 15 SPELL_DEMI = 16 SPELL_QUARTR = 17 SPELL_XZONE = 18
135 SPELL_METEOR = 19 SPELL_ULTIMA = 20 SPELL_QUAKE = 21 SPELL_WWIND = 22 SPELL_MERTON = 23
136
137 SPELL_SCAN = 24 SPELL_SLOW = 25 SPELL_RASP = 26 SPELL_MUTE = 27 SPELL_SAFE = 28
138 SPELL_SLEEP = 29 SPELL_MUDDLE = 30 SPELL_HASTE = 31 SPELL_STOP = 32 SPELL_BSERK = 33
139 SPELL_FLOAT = 34 SPELL_IMP = 35 SPELL_RFLECT = 36 SPELL_SHELL = 37 SPELL_VANISH = 38
140 SPELL_HASTE2 = 39 SPELL_SLOW2 = 40 SPELL_OSMOSE = 41 SPELL_WARP = 42 SPELL_QUICK = 43 -- None of the mods use Quick so lets just leave this off...
141 SPELL_DISPEL = 44
142
143 SPELL_CURE = 45 SPELL_CURE2 = 46 SPELL_CURE3 = 47 SPELL_LIFE = 48 SPELL_LIFE2 = 49
144 SPELL_ANTDOT = 50 SPELL_REMEDY = 51 SPELL_REGEN = 52 SPELL_LIFE3 = 53
145
146 SPELL_DARK = nil
147 SPELL_SLEEPX = nil
148 SPELL_REGENX = nil
149
150 SPELL_SPLASH = nil
151 SPELL_FLOOD = nil
152 SPELL_METEO = nil
153
154 -- enemy commands
155 SPELL_ESCAPE = 194
156
157 if (isROTDS) then
158 SPELL_SPLASH = 16
159 SPELL_FLOOD = 17
160 SPELL_METEO = 24
161
162 SPELL_QUARTR = 22 -- was 17
163 SPELL_SCAN = 43 -- was 24
164
165 SPELL_DEMI = nil -- was 16
166 SPELL_WWIND = nil -- was 22
167 SPELL_QUICK = nil -- was 43
168 elseif (isBNW) then
169 SPELL_DARK = 16
170 SPELL_SLEEPX = 30
171 SPELL_REGENX = 53
172
173 SPELL_BIO = 4 -- was 8
174 SPELL_BREAK = 8 -- was 12
175 SPELL_QUAKE = 12 -- was 21
176 SPELL_WWIND = 17 -- storm, was 22
177 SPELL_MERTON = 21 -- was 23
178 SPELL_DEMI = 22 -- was 16
179 SPELL_QUARTR = 23 -- was 17
180 SPELL_DRAIN = 24 -- was 4
181
182 SPELL_OSMOSE = 25 -- was 41
183 SPELL_MUDDLE = 27 -- was 30
184 SPELL_MUTE = 28 -- was 27
185 SPELL_IMP = 31 -- was 35
186 SPELL_BSERK = 32 -- was 33
187 SPELL_STOP = 33 -- was 32
188 SPELL_SAFE = 34 -- was 28
189 SPELL_SHELL = 35 -- was 37
190 SPELL_HASTE = 36 -- was 31
191 SPELL_HASTE2 = 37 -- was 39
192 SPELL_SLOW = 38 -- was 25
193 SPELL_SLOW2 = 39 -- was 40
194 SPELL_RFLECT = 40 -- was 36
195 SPELL_FLOAT = 41 -- was 34
196 SPELL_SCAN = 43 -- was 24
197
198 SPELL_LIFE3 = 50 -- was 53
199
200 SPELL_VANISH = nil -- was 38
201 SPELL_ANTDOT = nil -- was 50
202 SPELL_QUICK = nil -- was 43
203 end
204
205 -- proper spellings/alt names
206 SPELL_BERSERK = SPELL_BSERK
207 SPELL_HOLY = SPELL_PEARL
208 SPELL_QUARTER = SPELL_QUARTR
209 SPELL_REFLECT = SPELL_RFLECT
210 SPELL_ANTIDOTE = SPELL_ANTDOT
211 SPELL_RERAISE = SPELL_LIFE3
212
213 -- BNW naming
214 SPELL_SAP = SPELL_POISON
215 SPELL_STORM = SPELL_WWIND
216 -- ROTDS naming
217 SPELL_DEMI3 = SPELL_QUARTR
218
219 -- BNW has no: Vanish, Antidote, Quick
220 -- ROTDS has no: Demi, W.Wind, Quick
221 --print("Magic re-configured.")
222end
223configSpells()
224
225TEXT_DELAY = string.char(0x05) .. string.char(0x00)
226TEXT_NEWLINE = string.char(0x01) .. string.char(0x00)
227
228SKILL_FIGHT = 0x00 SKILL_ITEM = 0x01 SKILL_MAGIC = 0x02 SKILL_MORPH = 0x03
229SKILL_REVERT = 0x04 SKILL_STEAL = 0x05 SKILL_MUG = 0x06 SKILL_SWDTECH = 0x07
230SKILL_THROW = 0x08 SKILL_TOOLS = 0x09 SKILL_BLITZ = 0x0A SKILL_RUNIC = 0x0B
231SKILL_LORE = 0x0C SKILL_SKETCH = 0x0D SKILL_CONTROL = 0x0E SKILL_SLOT = 0x0F
232SKILL_RAGE = 0x10 SKILL_LEAP = 0x11 SKILL_MIMIC = 0x12 SKILL_DANCE = 0x13
233SKILL_ROW = 0x14 SKILL_DEFEND = 0x15 SKILL_JUMP = 0x16 SKILL_XMAGIC = 0x17
234SKILL_GPRAIN = 0x18 SKILL_SUMMON = 0x19 SKILL_HEALTH = 0x1A SKILL_SHOCK = 0x1B
235SKILL_POSSESS = 0x1C SKILL_MAGITEK = 0x1D
236SKILL_NONE = 0x32 -- does nothing (skips turn)
237SKILL_EVENT_BATTLE_MESSAGE = 0x21 -- F3, can use to display dialogs if needed.. / #$10
238SKILL_EVENT_BATTLE_SYSTEM_MESSAGE = 0x25 -- displays system msg instead of enemy dialog / #$02
239SKILL_EVENT_BATTLE_EVENT = 0x23 -- F7
240SKILL_EVENT_F2 = 0x20 -- "Replacement template assigner." - formation change (limited compatibility)
241SKILL_EVENT_F5 = 0x24 -- "Enemy/Template Manipulator - F5 0C 01 FF" causes the monster to die like a boss."
242SKILL_EVENT_F8 = 0x2E -- "Variable manipulation, where xx is the variable ID, y is the operation, and z is the value. The default value of each variable is 0."
243SKILL_EVENT_F9 = 0x2F -- https://datacrystal.romhacking.net/wiki/Final_Fantasy_VI:Monster_Script_Format#F9:_Bit_Variable_manipulation
244SKILL_EVENT_FA = 0x2B -- https://datacrystal.romhacking.net/wiki/Final_Fantasy_VI:Monster_Script_Format#FA:_Use_animation
245SKILL_EVENT_FB = 0x30 -- https://datacrystal.romhacking.net/wiki/Final_Fantasy_VI:Monster_Script_Format#FB:_Miscellaneous
246SKILL_EVENT_SCAN_INFO = 0x27
247-- SKILL_EVENT_RUNAWAY = 0x2A -- doesn't actually work lol
248-- custom skills
249-- cmd ptrs are at C2/19C7
250CUSTOMSKILL_STEALTH = 0x20
251CUSTOMSKILL_BOOK = 0x21
252CUSTOMSKILL_do_not_use = 0x22 -- regen triggers use 0x22
253
254
255
256-- cmd ptrs are at C2/19C7
257SKILL_SLOTS = SKILL_SLOT
258
259TARGET_CAN_MOVE_CURSOR = 1 -- player can move cursor
260TARGET_CANT_CHANGE_SIDES = 2 -- cursor is locked to prevent switching from ally/enemy (use only this for self-target)
261TARGET_HITS_EVERYONE = 4 -- skill will hit entire battle (use only this for quake etc) (combos w/ whole party to hit regardless of loc (pincer/side))
262TARGET_HITS_WHOLE_PARTY = 8 -- auto-targets entire party
263TARGET_AUTOCONFIRM = 16 -- confirms automatically (blitz/bushido)
264TARGET_CAN_MULTITARGET = 32 -- allow player to switch to target entire party (use for all except ST?)
265TARGET_ENEMY_SIDE = 64 -- targets enemies by default
266TARGET_RANDOM = 128 -- random cursor (death roulette)
267
268TARGETS_HITS_ALL_ALLIES = (TARGET_HITS_EVERYONE + TARGET_HITS_WHOLE_PARTY + TARGET_CANT_CHANGE_SIDES) -- hits all allies even in a side attack
269TARGETS_SINGLE_TARGET_HOSTILE = (TARGET_CAN_MOVE_CURSOR + TARGET_ENEMY_SIDE) -- allows targetting a single enemy (can switch sides to ally)
270TARGETS_SINGLE_TARGET_FRIENDLY = (TARGET_CAN_MOVE_CURSOR) -- allows targetting a single ally (can switch sides to enemy)
271TARGETS_SINGLE_ENEMY = (TARGET_CAN_MOVE_CURSOR + TARGET_ENEMY_SIDE + TARGET_CANT_CHANGE_SIDES) -- allows targetting one enemy (no ally)
272TARGETS_SINGLE_ALLY = (TARGET_CAN_MOVE_CURSOR + TARGET_CANT_CHANGE_SIDES) -- allows targetting one ally (no enemy)
273TARGETS_SELF = (TARGET_CANT_CHANGE_SIDES)
274
275function hexformat(v) return string.format("%02X",v) end
276function dumpRegister(r)
277 local x = memory.getregister(r)
278 print(r .. "/" .. x .. "/" .. string.format("%02X",x))
279end
280function dumpRegisters()
281 print("----------------")
282 local registers = { "db", "p", "e", "a", "d", "s", "x", "y", "pb", "pc", "pbpc" }
283 -- db = what memory bank we're reading/writing to (00 = $0000-FFFF, 7E = $7E0000-7EFFFF)
284 --pbpc = currently executing command
285 for i,v in pairs(registers) do
286 dumpRegister(v)
287 end
288 print("----------------")
289end
290InputLastFrame = {}
291Input = {}
292InputHeld = {}
293InputRecover = nil
294
295function onPress(key)
296 return (Input[key] and (not InputLastFrame[key]))
297end
298function onRepeat(key)
299 -- minimum 12 frame hold (1/5 second), and only every other frame, or every frame after 60 frame hold
300 return (InputHeld[key] and ((InputHeld[key] > 12 and InputHeld[key] % 2 == 1) or (InputHeld[key] > 60)))
301end
302function press(key)
303 return Input[key]
304end
305function checkInput()
306 InputLastFrame = Input
307 Input = joypad.get()
308 for i,v in pairs(InputLastFrame) do -- allow for repeating
309 if (Input[i] and InputLastFrame[i]) then
310 InputHeld[i] = (InputHeld[i] and (InputHeld[i] + 1)) or 1
311 else
312 InputHeld[i] = nil
313 end
314 end
315end
316
317
318local fontMap = {
319 [0x01] = "\n",
320 [0x80] = "A", [0x81] = "B", [0x82] = "C", [0x83] = "D", [0x84] = "E", [0x85] = "F", [0x86] = "G",
321 [0x87] = "H", [0x88] = "I", [0x89] = "J", [0x8A] = "K", [0x8B] = "L", [0x8C] = "M", [0x8D] = "N",
322 [0x8E] = "O", [0x8F] = "P", [0x90] = "Q", [0x91] = "R", [0x92] = "S", [0x93] = "T", [0x94] = "U",
323 [0x95] = "V", [0x96] = "W", [0x97] = "X", [0x98] = "Y", [0x99] = "Z",
324 [0x9A] = "a", [0x9B] = "b", [0x9C] = "c", [0x9D] = "d", [0x9E] = "e", [0x9F] = "f", [0xA0] = "g",
325 [0xA1] = "h", [0xA2] = "i", [0xA3] = "j", [0xA4] = "k", [0xA5] = "l", [0xA6] = "m", [0xA7] = "n",
326 [0xA8] = "o", [0xA9] = "p", [0xAA] = "q", [0xAB] = "r", [0xAC] = "s", [0xAD] = "t", [0xAE] = "u",
327 [0xAF] = "v", [0xB0] = "w", [0xB1] = "x", [0xB2] = "y", [0xB3] = "z",
328 [0xB4] = "0", [0xB5] = "1", [0xB6] = "2", [0xB7] = "3", [0xB8] = "4", [0xB9] = "5", [0xBA] = "6",
329 [0xBB] = "7", [0xBC] = "8", [0xBD] = "9",
330 [0xBE] = "!", [0xBF] = "?", [0xC0] = "/", [0xC1] = ":", [0xC2] = '"', [0xC3] = "'", [0xC4] = "-",
331 [0xC5] = ".", [0xC6] = ",", [0xC7] = "_", [0xC8] = ";", [0xC9] = "#", [0xCA] = "+", [0xCB] = "(",
332 [0xCC] = ")", [0xCD] = "%",
333 [0xCE] = "~", -- / in rotds
334 [0xD2] = "=",
335 [0xE8] = "^", [0xE9] = "&", [0xEA] = "$", --E8-EA = white,black,gray magic
336 [0xFE] = " ",
337 }
338
339local bigFontMap = {
340 [0xCF] = "*",
341 [0xD1] = "^",--
342 --[0xD6-0xDE]: elements, except 57 (x)
343
344}
345local reverseBigFontMap = {}
346local reverseMap = {}
347for i,v in pairs(fontMap) do
348 if (not bigFontMap[i]) then
349 bigFontMap[i] = v
350 end
351 reverseMap[v] = i
352end
353function getA() return bit.band(0x00FF,memory.getregister("a")) end -- low byte
354function getB() return bit.band(0xFF00,memory.getregister("a")) / 0x100 end -- high byte
355function setA(v) memory.setregister("a",v + (getB() * 0x100)) end -- low byte
356function setB(v) memory.setregister("a",(v * 0x100) + getA()) end -- high byte
357
358function setBit(a,b) return bit.bor(a,b) end
359function clearBit(a,b) return bit.band(a,bit.bnot(b)) end
360function checkBit(a,b) return (bit.band(a,b) == b) end
361function anyBit(a,b) return (bit.band(a,b) > 0) end
362
363function findEffect(name)
364 if (type(name) == "table") then return name end
365 for i,v in pairs(Effects) do
366 if (i.id == name or i == name or i.name == name) then return v end
367 end
368 return
369end
370function getTargetTable(bits)
371 local allies = { 0x01, 0x02, 0x04, 0x08 }
372 local enemies = { 0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000 }
373 local out = { allies = {}, enemies = {} }
374 for i,v in pairs(allies) do if (bit.band(bits,v) > 0) then out.allies[#out.allies + 1] = i - 1 end end
375 for i,v in pairs(enemies) do if (bit.band(bits,v) > 0) then out.enemies[#out.enemies + 1] = i - 1 end end
376 return out
377end
378
379-- converts 0,1,2,3 to actor id number (0 = terra etc)
380function getActorFromSlotID(slot)
381 if (slot < 4) then
382 return memory.readbyte(combatPointers.actorIndex + (slot*2))
383 end
384end
385function getMonsterFromSlotID(slot)
386 if (slot > 3) then
387 local idx = memory.readbyte(0x7E3F46 + (slot-4)) -- 3F46 is first monster index
388 local msbPos = slot-4 -- first monster (slot 4) = 0x20 (bit 5), last = 0x01 (bit 0)
389 msbPos = math.pow(2,msbPos)
390 local msbData = memory.readbyte(0x7E3F52)
391 if (bit.band(msbData, msbPos) > 0) then idx = idx + 0x100 end
392 if (idx == 0x1FF) then return end -- no enemy in slot
393 return idx
394 end
395end
396
397isROTDS = false
398isBNW = false
399isVanilla = false
400
401function checkRomType()
402 local romName = memory.readbyterange(0xC0FFC0,20) -- internal rom name
403 local name = ""
404 local wasROTDS = isROTDS
405 local wasBNW = isBNW
406 local wasVanilla = isVanilla
407
408 for i,v in pairs(romName) do name = name..string.char(v) end
409 name = name:lower()
410 isROTDS = false
411 isBNW = false
412 isVanilla = false
413 if (name:sub(1,5) == "rotds") then
414 isROTDS = true
415 else -- todo: fix after 2.0.1
416 isBNW = true
417 end
418 if (isROTDS and not wasROTDS) then configSpells()
419 elseif (isBNW and not wasBNW) then configSpells()
420 elseif (isVanilla and not wasVanilla) then configSpells()
421 end
422end
423hBlank = false
424vBlank = false
425memory.registerread(0x004212,function()
426 local info = memory.readbyte(0x004212)
427 hBlank = checkBit(info,0x40)
428 vBlank = checkBit(info,0x80)
429end)
430
431MODE_OTHER = 0
432MODE_BATTLE = 1
433MODE_MENU = 2
434MODE_FIELD = 3
435MODE_WORLD = 4
436
437function checkGameWorld()
438 local NMIByte = bit.band(0x00FFFFFF,memory.readdword(0x7E1501))
439 inBattle, inMenu, inField, inWorld = false, false, false, false
440 CurrentState = nil
441 if (NMIByte == 0xC10BA7) then
442 mode = MODE_BATTLE
443 CurrentState = BattleState
444 inBattle = true
445 elseif (NMIByte == 0xC31387) then
446 mode = MODE_MENU
447 CurrentState = MenuState
448 inMenu = true
449 elseif (NMIByte == 0xC00182) then -- also immediately on boot
450 mode = MODE_FIELD
451 inField = true
452 elseif (NMIByte == 0xEEA728) then -- walking-world
453 mode = MODE_WORLD
454 inWorld = true
455 elseif (NMIByte == 0xEEA509) then -- flight-world
456 mode = MODE_WORLD
457 inWorld = true
458 else
459 mode = MODE_OTHER
460 end
461 if (not inBattle) then BattleState = {} end
462 if (not inMenu) then MenuState = {} end
463end
464
465combatPointers = {
466 atbGauge = 0x3218, -- 2 bytes
467 generalFlags1 = 0x3AA0, -- 0x80: menu can open, 0x01: character is in battle, 0x02: skip wait gauge
468 generalFlags2 = 0x3AA1, -- 0x04: ID immune, 0x02: defending, 0x20: row(0=front), 0x10: proc DoT
469 waitCounter = 0x3AB4, -- 2 bytes
470
471 timeCounter = 0x3ADC, -- (decrement status counters on overflow)
472 timeConstant = 0x3ADD,
473
474 dotCounter = 0x3AF0,
475 stopCounter = 0x3AF1,
476 morphGauge = 0x3B04,
477 condemnedCount = 0x3B05,
478
479 level = 0x3B18,
480 speed = 0x3B19,
481 vigor = 0x3B2C, -- multiplied by 2
482 --speedFake = 0x3B2D, -- unused?
483 stamina = 0x3B40,
484 magicPower = 0x3B41, -- multiplied by 1.5
485 physChanceToBeHit = 0x3B54, -- 255 - (Evade * 2) + 1
486 magicChanceToBeHit = 0x3B55, -- 255 - (MBlock * 2) + 1
487 mainHandAttack = 0x3B68,
488 offHandAttack = 0x3B69,
489 hitRate = 0x3B7C, -- 2 bytes
490 mainHandElement = 0x3B90,
491 offHandElement = 0x3B91,
492 mainHandWeaponProperties = 0x3BA4, -- 0x02 = swdtech, 0x20 = back row ok, 0x40 = gauntlet ok, 0x80 = runic ok
493 offHandWeaponProperties = 0x3BA5,
494 defense = 0x3BB8,
495 mDefense = 0x3BB9,
496 absorbedElements = 0x3BCC,
497 immuneElements = 0x3BCD,
498 weakElements = 0x3BE0,
499 halfElements = 0x3BE1,
500 currentHP = 0x3BF4, -- 2 bytes
501 currentMP = 0x3C08, -- 2 bytes
502 maxHP = 0x3C1C, -- 2 bytes
503 maxMP = 0x3C30, -- 2 bytes
504
505 relicEffects1 = 0x3C44,
506 relicEffects2 = 0x3C45,
507 relicEffects3 = 0x3C58,
508 relicEffects4 = 0x3C59,
509
510
511 equipmentStatus = 0x3C6C, -- 2 bytes, status 2/3
512 specialStatus2 = 0x3C80, -- 0x80 = noCtrl, 0x40 = ?, 0x20 = noSketch, 0x10 = noScan, 0x08 = noRun, 0x04 = noSuplex, 0x02 = firstStrike, 0x01 = hardRun
513 -- specialAttackAnimationIndex = 0x3C81,
514 metamorphData = 0x3C94,
515 monsterType = 0x3C95, -- 0x80 = undead, 0x40 = impCritical, 0x10 = human, 0x04 = noName, 0x01 = 0MP=Death
516 mainHandWeapon = 0x3CA8, -- used for attack graphic, spear check for jump (#$1D~#$25)..
517 offHandWeapon = 0x3CA9,
518 mainHandWeaponBlockInfo = 0x3CBC, -- 0x03 = block graphic (0=dag,1=swd,2=shd,3=cape), 0x04 = canBlockPhys, 0x08 = canBlockMagic, 0xF0 = specialEffect?
519 offHandWeaponBlockInfo = 0x3CBD,
520 relic1 = 0x3CD0,
521 relic2 = 0x3CD1,
522 physicalBlockGraphics = 0x3CE4, -- 0x01 = dagger, 0x02 = sword, 0x04 = shield, 0x08 = cape
523 magicalBlockGraphics = 0x3CE5,
524 spellCount = 0x3CF8,
525 sleepCounter = 0x3CF9,
526 mainHandWeaponSpell = 0x3D34, -- 0x40 = can random spellcast, 0x80 = break on item-use cast?
527 offHandWeaponSpell = 0x3D35,
528 recentlyAttackedByCommand = 0x3D48,
529 recentlyAttackedBySpell = 0x3D49,
530 recentlyAttackedByItem = 0x3D5C,
531 recentlyAttackedByElement = 0x3D5D,
532 runCounter = 0x3D70,
533 runChance = 0x3D71,
534 experience = 0x3D84, -- 2 bytes
535 gold = 0x3D98, -- 2 bytes (probably)
536 characterVariable = 0x3DAC, -- 2 bytes (???), 0x80 set when casting Seize
537 characterTimer = 0x3DC0, -- 2 bytes, lifetime
538
539 statusToSet1 = 0x3DD4,
540 statusToSet2 = 0x3DD5,
541 statusToSet3 = 0x3DE8,
542 statusToSet4 = 0x3DE9,
543 statusToClear1 = 0x3DFC,
544 statusToClear2 = 0x3DFD,
545 statusToClear3 = 0x3E10,
546 statusToClear4 = 0x3E11,
547
548 poisonMultiplier = 0x3E24,
549 specialStatus1 = 0x3E4C, -- 0x80 = piranha, 0x10 = just procced poison, 0x08 = proc'd regen/seize/phantasm, 0x04 = runic cmd, 0x02 = enemy runic (permanent?), 0x01 = retort
550 uniqueStatus = 0x3E4D, -- 0x40 = phantasm, 0x02 = overcast, 0x01 = controlling something
551
552 status1 = 0x3EE4, --
553 status2 = 0x3EE5,
554 status3 = 0x3EF8,
555 status4 = 0x3EF9,
556
557 reflectCounter = 0x3F0C,
558 freezeCounter = 0x3F0D,
559
560 actorIndex = 0x3ED8, -- players only
561 actorGraphic = 0x3ED9,
562
563 characterMask = 0x3018,
564
565 -- could use for.. things? for consistency with Rewind...
566 unusedA = 0x3E25, -- 3E24 is poison multiplier
567 unusedB = 0x3E38,
568 unusedC = 0x3E39,
569 unusedD = 0x3E88,
570 unusedE = 0x3E89,
571 unusedF = 0x3E9C,
572 unusedG = 0x3E9D,
573}
574twoBytePointers = { atbGauge = true, waitCounter = true, hitRate = true,
575 currentHP = true, currentMP = true, maxHP = true, maxMP = true,
576 equipmentStatus = true, experience = true,
577 gold = true, characterVariable = true, characterTimer = true,
578 }
579for i,v in pairs(combatPointers) do combatPointers[i] = 0x7E0000 + v end -- they should be in memory
580
581-- No information is stored whatsoever in BattleActors
582BattleActors = {}
583
584-- when creating new actors, use > setmetatable(newactor, BattleActor._meta) <
585BattleActor = {}
586BattleActor._meta = {}
587BattleActor._meta.__index = function(self,key)
588 -- if ._meta.<whatever> exists then just do that (e.g. :applyStatus)
589 local f = rawget(BattleActor._meta,key)
590 if (f) then return f end
591 local i = rawget(self,"entityIndex")
592 if (not i) then return end
593 if (key == "Effects") then
594 if (not BattleState.Effects) then BattleState.Effects = {} end
595 if (not BattleState.Effects[i]) then BattleState.Effects[i] = {} end
596 return BattleState.Effects[i]
597 end
598 local offset = combatPointers[key]
599 if (not offset) then
600 return rawget(self,key)
601 end
602 offset = offset + i*2
603 local val = nil
604 if (twoBytePointers[key]) then
605 val = memory.readword(offset)
606 else
607 val = memory.readbyte(offset)
608 end
609 return val
610end
611BattleActor._meta.__newindex = function(self, key, value)
612 local i = self.entityIndex
613 local offset = combatPointers[key]
614 if (not offset) then
615 rawset(self,key,value)
616 return
617 end
618 offset = offset + i*2
619 if (twoBytePointers[key]) then
620 memory.writeword(offset,value)
621 else
622 memory.writebyte(offset,value)
623 end
624end
625
626-- "key" for status functions are 'statusList.whatever', {a,b} pairs where a is which status byte (1,2,3,4) and b is flag
627function BattleActor._meta:hasStatus(key)
628 local myStatus = self["status"..key[1]]
629 return checkBit(myStatus,info[2])
630end
631function BattleActor._meta:hasAnyStatus(key)
632 local myStatus = self["status"..key[1]]
633 return anyBit(myStatus,info[2])
634end
635function BattleActor._meta:applyStatus(key)
636 local myStatus = self["status"..key[1]]
637 self["status"..key[1]] = setBit(myStatus,key[2])
638end
639function BattleActor._meta:removeStatus(key)
640 local myStatus = self["status"..key[1]]
641 self["status"..key[1]] = clearBit(myStatus,key[2])
642end
643
644function BattleActor._meta:hasEffect(effect)
645 effect = findEffect(effect)
646 if (not effect) then return false end
647 if (not inBattle) then return false end -- fix up later
648 if (self.Effects[effect.id]) then return true end
649 return false
650end
651function BattleActor._meta:applyEffect(effect) -- false on failed to apply, true on success
652 effect = findEffect(effect)
653 if (not effect) then return false end
654 if (not inBattle) then return false end -- fix up later
655 if (self:hasEffect(effect) and effect.doReapply) then
656 return effect.doReapply(self.entityIndex)
657 else
658 self.Effects[effect.id] = true
659 return effect.doApply(self.entityIndex)
660 end
661end
662function BattleActor._meta:removeEffect(effect)
663 effect = findEffect(effect)
664 if (not effect) then return false end
665 if (not self:hasEffect(effect)) then return false end -- doesn't even have it!
666 self.Effects[effect.id] = nil
667 return effect.doRemove(self.entityIndex)
668end
669function BattleActor._meta:doEffectTrigger(trigger)
670 for i,v in pairs(self.Effects) do
671 local effect = findEffect(effect)
672 if (effect and effect.triggers and effect.triggers[trigger]) then
673 effect.triggers[trigger](self.entityIndex)
674 return true
675 end
676 end
677end
678
679
680
681function updateBattleState() -- run every frame, stores all info in battle state
682 if (not inBattle) then return end
683 for i=0,9 do
684 if (not BattleActors[i]) then
685 local actor = {}
686 actor.entityIndex = i
687 setmetatable(actor,BattleActor._meta)
688 BattleActors[i] = actor
689 end
690 end
691 local chrTurn = memory.readbyte(0x7E62CA)
692 local chrIdx = getActorFromSlotID(chrTurn)
693 BattleState.CurrentCharacter = chrIdx
694 BattleState.CurrentCharacterSlot = chrTurn
695end
696
697--[=[
698
699
700 Command Functions
701
702
703--]=]
704function getStealthName()
705 if (inBattle and BattleActors[BattleState.CurrentCharacterSlot]:hasEffect(Effects.Stealth)) then return "Reveal" end
706 return "Stealth"
707end
708function doStealthCommand()
709 local vics = BattleState.CurrentActionTargetTable
710 for i,entity in pairs(vics.allies) do
711 if (BattleActors[entity]:hasEffect(Effects.Stealth)) then
712 -- ..unstealth
713 BattleActors[entity]:removeEffect(Effects.Stealth)
714 else
715 BattleActors[entity]:applyEffect(Effects.Stealth)
716 end
717 end
718end
719function checkStealthIsBlocked(chr)
720 local playersAlive = memory.readbyte(0x7E3A76)
721 if (playersAlive < 2) then return true end
722end
723function doStealthPostStatus(chr)
724 local playersAlive = memory.readbyte(0x7E3A76)
725 local curHp = memory.readword(combatPointers.currentHP + (chr*2))
726 if (playersAlive < 2) then
727 BattleActors[chr]:removeEffect(Effects.Stealth)
728 elseif (curHp <= 0) then -- died (probably from poison)
729 BattleActors[chr]:removeEffect(Effects.Stealth)
730 BattleActors[chr]:applyStatus(statusList.dead)
731 end
732end
733function doGetFightName()
734 local entity = ((inBattle) and BattleState.CurrentCharacterSlot) or (inMenu and getCurrentMenuCharacter())
735 if (not entity) then return "Fight" end
736 if (inBattle and BattleActors[entity]:hasEffect(Effects.Stealth)) then return "Ambush"
737 else
738 return "Fight"
739 end
740end
741function doAmbush(chr)
742 forceCritical()
743 BattleActors[chr]:removeEffect(Effects.Stealth)
744end
745function forceCritical()
746 BattleState.ForceCrit = true
747end
748
749function doNothing() end -- does.. literally nothing, but here so triggers still work right
750
751--[[
752 id - identifier in buff tables; matching will generally be overridden
753 name - name shown in lists
754 doReapply - applies when already applied
755 triggers - integer = cmd id
756 anyOtherCommand triggers if its any cmd besides a triggered command
757 anyCommand triggers for any command period
758--]]
759Effects = {}
760Effects.Stealth = {
761 id = "STEALTH",
762 name = "Stealth",
763 doApply = function(chr)
764 BattleActors[chr]:applyStatus(statusList.vanish)
765 local m = BattleActors[chr].characterMask
766 local hidden = memory.readword(0x7E3F2C)
767 local redraw = memory.readword(0x7E3A58)
768 memory.writeword(0x7E3F2C,setBit(hidden,m))
769 memory.writeword(0x7E3A58,setBit(redraw,m))
770 end,
771 doReapply = function(chr) end, -- can't reapply
772 doRemove = function(chr)
773 BattleActors[chr]:removeStatus(statusList.vanish)
774 local m = BattleActors[chr].characterMask
775 local hidden = memory.readword(0x7E3F2C)
776 local redraw = memory.readword(0x7E3A58)
777 memory.writeword(0x7E3F2C,clearBit(hidden,m))
778 memory.writeword(0x7E3A58,setBit(redraw,m))
779 end,
780 triggers = {
781 [SKILL_FIGHT] = doAmbush,
782 [CUSTOMSKILL_STEALTH] = doNothing, -- don't instantly strip the stealth..
783 [SKILL_ROW] = doNothing, -- permit row change
784 anyOtherCommand = (function(chr) BattleActors[chr]:removeEffect(Effects.Stealth) end),
785 postAppliedStatuses = doStealthPostStatus,
786 },
787 blockCommands = {
788 allExcept = {
789 [CUSTOMSKILL_STEALTH] = true,
790 --[SKILL_ROW] = true, -- .. this isn't blockable anyways
791 },
792 },
793}
794--memory.registerread(0x7E202F,function() dumpRegister("pbpc") end) -- pbpc/12666965/C14855, C17A68
795--[[
796 proxyClick - click behavior acts as specified cmd (open menu, etc.. cmd still submits as custom ID)
797 proxyClickOverride - click behavior acts AND SUBMITS as specified cmd
798 proxyCmd - final execution is as this cmd (overridden by clickOverride)
799 proxySpell - final execution is as this spell (overridden by clickOverride)
800 func - executed function before performing proxyCmd/Spell (if they exist)
801 checkBlocked - executed function to determine if this one should be blocked
802 mimicable - flag indicating whether cmd should be stored for mimicry
803 target - targetting flags (added together), see above
804 proxyBlocked - if specified command is blocked, this cmd is blocked
805--]]
806
807customCommands = {
808 [0x00] = { name = doGetFightName, }, -- overrides "Fight" text with various potential effects
809 [CUSTOMSKILL_STEALTH] = {
810 name = getStealthName,
811 proxyClick = SKILL_FIGHT,
812 target = (TARGETS_SINGLE_ALLY),
813 proxyCmd = SKILL_NONE,
814 func = doStealthCommand,
815 checkBlocked = checkStealthIsBlocked,
816 -- not mimicable
817 retarget = true,
818 },
819 [CUSTOMSKILL_BOOK] = {
820 name = "Book",
821 proxyClick = SKILL_MAGIC,
822 proxyCmd = SKILL_MAGIC,
823 proxyBlocked = SKILL_MAGIC, -- disable if character is muted
824 mimicable = true, -- test this out..
825 gogoEquip = true,
826 },
827
828 [0x22] = {}, -- do not use 0x22, it is reserved for regen/poison etc ticks
829}
830
831
832function tweakSketchChance()
833 -- lower = better (0xFF is basically a fail, 0x00 is basically a win)
834 -- todo: achievements
835 if (true) then return end
836 local a = memory.getregister("a")
837 a = a - 1
838 if (a < 0) then a = 0 end
839 memory.setregister("a",a)
840end
841memory.registerexec(0xC237B1,tweakSketchChance)
842
843
844
845--[[
846
847
848
849
850
851--]]
852
853function checkForForcedCrit()
854 if (BattleState.ForceCrit) then
855 memory.writebyte(0x7E00B3,clearBit(memory.readbyte(0x7E00B3),0x02))
856 end
857end
858memory.registerexec(0xC233F4, checkForForcedCrit)
859-- called immediately after any new statuses are set/cleared
860function processPostApplyStatus()
861 local chr = memory.getregister("y")/2
862 BattleActors[chr]:doEffectTrigger("postAppliedStatuses")
863end
864memory.registerexec(0xC243FC,processPostApplyStatus)
865
866function checkForBlockedCommands()
867 local blocked = false
868 local cmd = memory.readbyte(0x7E202E + memory.getregister("x"))
869 local entity = BattleState.CurrentCharacterSlot
870 if (not entity) then return end
871 -- if we have zero usable commands then the game will freeze.
872 -- this forces at least 'fight' to always be ok during 'except all'
873 local safeCommands = { [SKILL_FIGHT] = true, [SKILL_MAGITEK] = true, [SKILL_MIMIC] = true }
874 -- we aren't going to forcibly block magic if we're in the fanatic's tower
875 local fanaticByte = memory.readbyte(0x7E3EBB)
876 if (cmd == SKILL_MAGIC and (fanaticByte % 2 == 1)) then -- fanatic bit is 0x01
877 return false
878 end
879 if (customCommands[cmd] and customCommands[cmd].checkBlocked) then
880 local bchk = customCommands[cmd].checkBlocked(entity)
881 if (bchk) then blocked = true end
882 end
883 if (not blocked) then
884 local effects = BattleActors[entity].Effects
885 if (effects) then
886 for i,v in pairs(effects) do
887 local effect = findEffect(effect)
888 if (effect and effect.blockCommands) then
889 local bc = effect.blockCommands
890 if (bc.allExcept and safeCommands[cmd]) then blocked = false break end
891 if (bc.allExcept and not bc.allExcept[cmd]) then blocked = true break end
892 if (bc[cmd]) then blocked = true break end
893 end
894 end
895 end
896 end
897 if (blocked) then
898 local a = memory.getregister("a")
899 a = setBit(a,0x80)
900 memory.setregister("a",a)
901 return a
902 end
903 return false
904end
905function checkForBlockedCommandInput()
906 -- ..this likes to get run twice thanks to it being on a single-byte instruction =__=
907 -- we only want it to run once.. if it runs twice we will get a hardlock
908 if (memory.getregister("pc") ~= 0x7A6A) then return end
909 local block = checkForBlockedCommands()
910 if (block) then
911 memory.setregister("pc",0x7A6D)
912 end
913end
914-- allows configuring a command to treat whether its blocked or not based on some other cmd's blockstate
915-- (e.g. allow a magic replacement to be blocked if Magic would be)
916function checkProxyBlockedCommand()
917 local cmd = getA()
918 if (customCommands[cmd] and customCommands[cmd].proxyBlocked) then
919 setA(customCommands[cmd].proxyBlocked)
920 end
921end
922memory.registerexec(0xC14855,checkForBlockedCommands)
923memory.registerexec(0xC17A6A,checkForBlockedCommandInput)
924memory.registerexec(0xC252AE,checkProxyBlockedCommand)
925
926
927function updateMenuState()
928
929end
930-- 100s value is which byte (1,2,3,4)
931statusList = {
932 blind = { 1, 0x01 }, zombie = { 1, 0x02 }, poison = { 1, 0x04 }, magitek = { 1, 0x08 },
933 vanish = { 1, 0x10 }, imp = { 1, 0x20 }, petrify = { 1, 0x40 }, dead = { 1, 0x80 },
934 condemned = { 2, 0x01 }, nearFatal = { 2, 0x02 }, blink = { 2, 0x04 }, mute = { 2, 0x08 },
935 berserk = { 2, 0x10 }, confusion = { 2, 0x20 }, sap = { 2, 0x40 }, sleep = { 2, 0x80 },
936 dance = { 3, 0x01 }, -- float on equipment
937 equipFloat = { 3, 0x01 }, regen = { 3, 0x02 }, slow = { 3, 0x04 }, haste = { 3, 0x08 },
938 stop = { 3, 0x10 }, shell = { 3, 0x20 }, safe = { 3, 0x40 }, reflect = { 3, 0x80 },
939 rage = { 4, 0x01 }, frozen = { 4, 0x02 }, reraise = { 4, 0x04 }, morph = { 4, 0x08 },
940 chant = { 4, 0x10 }, hidden = { 4, 0x20 }, interceptor = { 4, 0x40 }, float = { 4, 0x80 },
941}
942
943
944
945
946
947function replaceCommandNameBattle()
948--[[
949C1/69FE: BFA0CED8 LDA $D8CEA0,X (Loads Battle command name X)
950C1/6A02: 20F366 JSR $66F3 (draw tile A)
951 C1/66F3: 914C STA ($4C),Y
952 C1/66F5: A9FF LDA #$FF
953 C1/66F7: 914A STA ($4A),Y
954 C1/66F9: C8 INY
955 C1/66FA: A54E LDA $4E
956 C1/66FC: 914C STA ($4C),Y
957 C1/66FE: 914A STA ($4A),Y
958 C1/6700: C8 INY
959 C1/6701: 60 RTS
960C1/6A05: E8 INX
961C1/6A06: C610 DEC $10
962--]]
963 local cmdId = memory.readbyte(0x004216) / 7 -- 7*<id> = name position
964 if (not customCommands[cmdId]) then return end
965 local txt = customCommands[cmdId].name
966 if (type(txt) == "function") then txt = txt() end
967 if (not txt) then return end
968 local y = memory.getregister("y")
969 for i=0,7 do
970 local c = txt:sub(i+1,i+1) or nil
971 c = reverseMap[c] or 0xFE
972 local y2 = y + (i*2)
973 local loc1 = 0x7E0000 + memory.readword(0x7E004C) + y2
974 local loc2 = 0x7E0000 + memory.readword(0x7E004A) + y2
975 local fourE = memory.readbyte(0x7E004E)
976 memory.writebyte(loc1, c)
977 memory.writebyte(loc2, 0xFF)
978 memory.writebyte(loc1 + 1, fourE)
979 memory.writebyte(loc2 + 1, fourE)
980 end
981 memory.writebyte(0x7E0010,1)
982 memory.setregister("y",y + 14)
983 memory.setregister("pc",0x6A06) -- battle..
984end
985function getCurrentMenuCharacter() -- todo: cleanup
986 if (MenuState.GogoStatusMenu) then
987 MenuState.CurrentCharacter = 0x0C
988 return 0x0C
989 end
990 local curMenu = memory.readbyte(0x7E0026)
991 if (curMenu == 0x47 or curMenu == 0x48 or curMenu == 0x62 or curMenu == 0x63) then
992 -- do nothing, its caught below
993 else
994 MenuState.CurrentCharacter = memory.readbyte(0x7E0069 + memory.readbyte(0x000028))
995 end
996 return MenuState.CurrentCharacter
997end
998-- grabs correct character name for the short arrange menu
999function shortMenuCharacterCatch() MenuState.CurrentCharacter = memory.readbyte(0x7E0000 + memory.getregister("y")) end
1000memory.registerexec(0xc3459b,shortMenuCharacterCatch)
1001
1002function replaceCommandNameStatus()
1003 local cmdId = memory.getregister("x") / 7 -- 7*<id> = name position
1004 if (not customCommands[cmdId]) then return end -- only remove magic
1005 local txt = customCommands[cmdId].name
1006 if (type(txt) == "function") then txt = txt() end
1007 if (not txt) then return end
1008 for i=0,#txt do
1009 local c = txt:sub(i+1,i+1) or nil
1010 c = reverseMap[c] or 0xFE
1011 memory.writebyte(0x2180,c)
1012 end
1013 memory.writebyte(0x2180,0x00)
1014 memory.setregister("pc",0x5F05) -- skip past proper name reading
1015end
1016memory.registerexec(0xC169FE,replaceCommandNameBattle) -- battle command
1017memory.registerexec(0xC35EFF,replaceCommandNameStatus) -- status/short screen (gogo's cmds handled below mostly except for pre-scroll)
1018
1019
1020-- indicates whether or not some commands should be equippable by gogo
1021-- note that a command must be equipped for it to show up in gogo's list
1022function preventGogoCustomCommands()
1023 local x = memory.getregister("x")
1024 -- 00 = fight (can use), 03 = morph (can't use)
1025 if (x/2 >= 0x20) then
1026 local cc = customCommands[x/2]
1027 if (not cc) then memory.setregister("x",0x06) end -- 0x06 = 0x03(morph) * 2
1028 if (cc and cc.gogoEquip) then
1029 memory.setregister("x",0x00) -- sets current command check to fight (ie usable)
1030 elseif (cc) then
1031 memory.setregister("x",0x06) -- sets current command check to morph (ie gogo can't use)
1032 end
1033 end
1034end
1035memory.registerexec(0xC35E52,preventGogoCustomCommands)
1036
1037
1038-- sets gogo list size to 0x0E (15 items) if there are more than 15 items
1039memory.registerexec(0xC35EB1, function()
1040 local listSize = memory.readbyte(0x7E9D89)
1041 if (listSize >= 0x0E) then setA(0x0E) end
1042end)
1043-- sets right window height to maximum of 15 tiles
1044memory.registerexec(0xC35E91, function()
1045 if (getA() >= 0x15) then setA(0x15) end
1046end)
1047-- reposition the gogo box a little further down (offsets the first row being misaligned)
1048memory.registerexec(0xC35EA0, function()
1049 memory.writeword(0x7EAA8D, 0x58C7 + 0x80) -- left box, 02x20 at $58C7
1050 memory.writeword(0x7EAA91, 0x6087 + 0x80) -- right box, 09x20 at $6087
1051end)
1052-- reposition gogo list a little further down
1053memory.registerexec(0xC35EBC, function()
1054 local y = memory.getregister("y")
1055 memory.setregister("y",y + 0x80)
1056end)
1057-- uses (last list entry + 1) to store (scroll distance?)
1058memory.registerexec(0xC363BE, function()
1059 local cursorPos = memory.getregister("x")
1060 local listSize = memory.readbyte(0x7E9D89)
1061 cursorPos = cursorPos + memory.readbyte(0x7E9D8A + listSize)
1062 setA(memory.readbyte(0x7E9D8A + cursorPos))
1063end)
1064
1065-- does the fuckin' massive workload of handling scrolling and drawing names
1066memory.registerexec(0xC3640C, function()
1067 local listSize = memory.readbyte(0x7E9D89)
1068 local offsetAfterList = 0x7E9D8A + listSize
1069 local afterList = memory.readbyte(offsetAfterList)
1070 memory.writebyte(0x7E1508,afterList)
1071 local cursorPos = memory.readbyte(0x7E004E)
1072 cursorPos = cursorPos - (memory.readbyte(0x7E1508))
1073 if (cursorPos >= 0x00 and cursorPos < 0x0E) then -- don't care if it's within bounds of list
1074 -- nothing
1075 else
1076 if (cursorPos >= 0x0E) then
1077 cursorPos = cursorPos - 0x0D
1078 end
1079 cursorPos = cursorPos + afterList
1080 memory.writebyte(offsetAfterList,cursorPos)
1081 memory.writebyte(0x7E1508, cursorPos)
1082 memory.writebyte(0x7E1509, cursorPos)
1083 memory.writebyte(0x7E150A, 0x00)
1084 memory.writeword(0x7E150B, 0x80C9 + 0x80)
1085 local cursorPos9 = memory.readword(0x7E1509) -- 7E1509
1086 local counter = 0
1087 memory.writebyte(0x7E0013,0x10) -- this byte allows the gogo menu to properly update. idk wtf it is or how it works but its original value of 0x08 prevented it from being able to update
1088 for counter=0,0x0D do
1089 -- $09 => x
1090 cursorPos9 = cursorPos9 + 1 -- $09
1091 memory.writeword(0x7E1509, cursorPos9)
1092 local cmp = memory.readword(0x7E9D8A + cursorPos9 - 1)
1093 local commandId = bit.band(0x00FF,cmp) -- a, 16-bit
1094 if (commandId ~= 0x00FF) then
1095 -- location of command text
1096 memory.writeword(0x7E150D, commandId) -- accumulator => $0D
1097 local cmIdMod = commandId
1098 cmIdMod = cmIdMod * 8
1099 cmIdMod = cmIdMod + 0xCEA0 -- ADC, but commandId can't be above 0xFF so carry can't be set here
1100 cmIdMod = cmIdMod - memory.readword(0x7E150D)
1101 memory.writeword(0x7E1538,cmIdMod)
1102 memory.writebyte(0x7E153A,0xD8)
1103 else -- i have no idea what this is supposed to be considering F block is unused?
1104 memory.writeword(0x7E1538,0xFCA0)
1105 memory.writebyte(0x7E153A,0xF0)
1106 end
1107 -- store $0B in $3B
1108 local ohBee = memory.readword(0x7E150B)
1109 memory.writeword(0x7E153B,ohBee)
1110 memory.writebyte(0x7E153D,0x07) -- store #$07 in $3D
1111 local x = memory.readword(0x7E153B) -- shift them down 0x80 (2 rows (includes the half-row between))
1112 local y = 0
1113 local txtPointer = bit.band(memory.readdword(0x7E1538),0x00FFFFFF)
1114 local ccName = (customCommands[commandId] and customCommands[commandId].name) or nil
1115 if (ccName) then
1116 if (type(ccName) == "function") then ccName = ccName() end
1117 end
1118 -- handles printing of battle command names
1119 local chrToPrint = memory.readbyte(0x7E153D)
1120 while (chrToPrint > 0) do
1121 -- loc should generally be in the D8Cxxx block
1122 if (ccName) then
1123 local chr = ccName:sub(8-chrToPrint,8-chrToPrint)
1124 chr = reverseMap[chr]
1125 local offLoc = 0x7E0000 + x
1126 memory.writebyte(offLoc, chr)
1127 else
1128 local loc = bit.band(memory.readdword(0x7E1538),0x00FFFFFF) + y
1129 local info = memory.readbyte(loc)
1130 memory.writebyte(0x7E0000 + x, info)
1131 end
1132 memory.writebyte(0x7E0001 + x, 0x20)
1133 x = x + 2
1134 y = y + 1
1135 chrToPrint = chrToPrint - 1
1136 memory.writebyte(0x7E153D,chrToPrint)
1137 end
1138 ohBee = memory.readword(0x7E150B)
1139 ohBee = ohBee + 0x80
1140 memory.writeword(0x7E150B,ohBee)
1141 -- end of loop
1142 end
1143 memory.writeword(0x7E1512,0x1000)
1144 end
1145 -- do new DPad Stuff - X&Y are 16-bit, A is 8-bit
1146 local store4E = memory.readbyte(0x7E004E) -- store 4E for later retrieval
1147 local listSize = memory.readbyte(0x7E9D89)
1148 local again4E = memory.readbyte(0x7E004E) -- get 4E again..
1149 again4E = again4E - memory.readbyte(0x7E9D8A + listSize)
1150 memory.writebyte(0x7E004E,again4E)
1151 --memory.writeword(0x7E00E7,0x6417) -- skip the first entry
1152 memory.writeword(0x7E00E7,0x6419)
1153 memory.writebyte(0x7E00E9,0xC3)
1154 -- ??? push register, load A 0x00, push A, pull register - using bank 00 from now on instead of 7E
1155 -- watch for plb at 00/01BA:
1156 -- hBlank check here but i think its fine without (can this even get called in non-hBlank?)
1157 local tmp = memory.readbyte(0x000053) * memory.readbyte(0x00004E) -- writes to 211B/211C => 2134 are just multiplication
1158 tmp = tmp + memory.readbyte(0x00004D)
1159 memory.writebyte(0x00004B, tmp)
1160 tmp = memory.readbyte(0x000053)
1161 tmp = tmp - 1
1162 local tmp2 = memory.readbyte(0x00004D)
1163 if (tmp >= tmp2) then -- carry set, jump 016C
1164 memory.writebyte(0x0000E0,memory.readbyte(0x000053))
1165 memory.writebyte(0x0000E2,memory.readbyte(0x00004D))
1166 else
1167 tmp = memory.readbyte(0x000053)
1168 tmp = tmp - 1
1169 tmp = tmp - (memory.readbyte(0x000051))
1170 memory.writebyte(0x0000E0,tmp)
1171 memory.writebyte(0x0000E2,tmp)
1172 end
1173 tmp = memory.readbyte(0x000054)
1174 tmp = tmp - 1
1175 local tmp2 = memory.readbyte(0x00004E)
1176 if (tmp >= tmp2) then -- carry set, jump 0185
1177 memory.writebyte(0x0000E1,memory.readbyte(0x00004E))
1178 else
1179 tmp = memory.readbyte(0x000054)
1180 tmp = tmp - 1
1181 tmp = tmp - (memory.readbyte(0x000052))
1182 memory.writebyte(0x0000E1,tmp)
1183 end
1184 -- another h-blank check, probably more 'while', would freeze ...
1185 tmp = memory.readbyte(0x0000E0) * memory.readbyte(0x0000E1)
1186 tmp = tmp + memory.readbyte(0x0000E2)
1187 tmp = tmp * 2
1188 local tmpY = tmp
1189 tmp = memory.readbyte(0xC36419 + tmpY) -- was 6417, moved to 6419
1190 memory.writebyte(0x000055,tmp)
1191 memory.writebyte(0x000056,0x00)
1192 tmpY = tmpY + 1
1193 tmp = memory.readbyte(0xC36419 + tmpY)
1194 memory.writebyte(0x000057,tmp)
1195 memory.writebyte(0x000058,0x00)
1196 tmpY = tmpY + 1
1197 memory.writebyte(0x7E004E,store4E)
1198 memory.setregister("pc",0x63FF)
1199end)
1200memory.registerexec(0xC213FA,function()
1201 -- upon skill activation
1202 -- C2/13FE: FC C7 19 JSR ($19C7,X)
1203
1204end)
1205memory.registerexec(0xC17CDA,function()
1206 -- upon clicking menu cursor
1207 -- C1/7CDE: 7CE97C JMP ($7CE9,X) (Jump to function for player Command)
1208 local cmdId = memory.getregister("a")
1209 local data = customCommands[cmdId]
1210 if (not data) then return end
1211 if (data.proxyClickOverride) then
1212 memory.writebyte(0x7E2BAF + memory.getregister("y"),data.proxyClickOverride)
1213 memory.setregister("a",data.proxyClickOverride)
1214 elseif (data.proxyClick) then
1215 memory.setregister("a",data.proxyClick)
1216 end
1217 if (data.target) then memory.writebyte(0x7E7A84, data.target) end
1218end)
1219function handleCustomMimic()
1220 -- stores targetting data for mimic cmd
1221 local cmd = BattleState.CurrentActionCommand
1222 if (cmd and cmd >= 0x20 and customCommands[cmd]) then
1223 if (customCommands[cmd].mimicable) then
1224 memory.writebyte(0x7E3A7C,BattleState.CurrentActionCommand)
1225 memory.writebyte(0x7E3A7D,BattleState.CurrentActionSpell)
1226 memory.writeword(0x7E3A30,BattleState.CurrentActionTarget)
1227 memory.setregister("pc",0x0235)
1228 memory.setregister("a",0x12)
1229 end
1230 end
1231end
1232memory.registerexec(0xC20227,handleCustomMimic)
1233function handleCustomRetarget()
1234 local cmd = getA()
1235 if (cmd == SKILL_MIMIC) then cmd = memory.readbyte(0x7E3F20) end
1236 if (customCommands[cmd] and customCommands[cmd].retarget) then
1237 setA(0x01)
1238 end
1239end
1240memory.registerexec(0xC24E32,handleCustomRetarget)
1241--[=[
1242
1243
1244
1245
1246
1247
1248--]=]
1249
1250-- returns if player is currently acting due to queue pop
1251function isPlayerActing()
1252 local ch = BattleState.CurrentActionCharacter
1253 return ch
1254end
1255
1256-- set immediately before checking for counterattack stuff to clear data
1257function clearCurrentAction()
1258 BattleState.CurrentActionCharacterSlot = nil
1259 BattleState.CurrentActionCharacter = nil
1260 BattleState.CurrentActionCommand = nil
1261 BattleState.CurrentActionSpell = nil
1262 BattleState.CurrentActionTarget = nil
1263 BattleState.CurrentActionTargetTable = nil
1264 BattleState.CurrentActionCost = nil
1265
1266 BattleState.ForceCrit = nil
1267 BattleState.CurrentActionIsMimic = false
1268end
1269memory.registerexec(0xc20059,clearCurrentAction) -- immediately before autoattack
1270
1271-- loads data for when an action is just about to be performed
1272function loadCurrentAction()
1273 clearCurrentAction()
1274 local y = memory.getregister("y")
1275 BattleState.CurrentActionCharacterSlot = memory.getregister("x") / 2
1276 BattleState.CurrentActionCharacter = getActorFromSlotID(BattleState.CurrentActionCharacterSlot)
1277 BattleState.CurrentActionCommand = memory.readbyte(0x7E3420 + y)
1278 if (BattleState.CurrentActionCommand == SKILL_MIMIC) then
1279 BattleState.CurrentActionCommand = memory.readbyte(0x7E3F20)
1280 BattleState.CurrentActionSpell = memory.readbyte(0x7E3F21)
1281 BattleState.CurrentActionTarget = memory.readword(0x7E3F22)
1282 BattleState.CurrentActionIsMimic = true
1283 else
1284 BattleState.CurrentActionSpell = memory.readbyte(0x7E3421 + y)
1285 BattleState.CurrentActionTarget = memory.readword(0x7E3520 + y)
1286 end
1287 BattleState.CurrentActionTargetTable = getTargetTable(BattleState.CurrentActionTarget)
1288 BattleState.CurrentActionCost = memory.readbyte(0x7E3620 + y)
1289 local ccs = BattleState.CurrentActionCharacterSlot
1290 if (isPlayerActing()) then
1291 --local cmdId = memory.readbyte(0x7E00B5)
1292 local data = customCommands[BattleState.CurrentActionCommand]
1293 if (data) then
1294 if (data.func) then
1295 if (type(data.func) == "function") then data.func(ccs) end
1296 end
1297 if (data.proxyCmd) then
1298 local pc = data.proxyCmd
1299 if (type(pc) == "function") then pc = pc(ccs) end
1300 memory.writebyte(0x7E3420 + y,pc)
1301 end
1302 if (data.proxySpell) then
1303 local ps = data.proxySpell
1304 if (type(ps) == "function") then ps = ps(ccs) end
1305 memory.writebyte(0x7E3421 + y,ps)
1306 end
1307 end
1308 local didCommandTrigger = BattleActors[ccs]:doEffectTrigger(BattleState.CurrentActionCommand)
1309 if (not didCommandTrigger) then BattleActors[ccs]:doEffectTrigger("anyOtherCommand") end
1310 BattleActors[ccs]:doEffectTrigger("anyCommand")
1311 end
1312end
1313memory.registerexec(0xC2010E,loadCurrentAction)
1314
1315
1316function main()
1317 checkGameWorld()
1318 checkInput()
1319 if (savingState) then
1320 local ss = savestate.create(900 + memory.readbyte(0x7e0224))
1321 savestate.save(ss)
1322 savingState = false
1323 end
1324 if (onPress("L") and inBattle) then
1325 --sourcelessSpellCast(0,240)
1326 -- memory.writebyte(0x7E3401,0xFF)
1327
1328 --function addToActionQueue(slot,cmd,spell,target,cost)
1329 --callCustomBattleMessage("QWERANYa, nya, nya, nya\nNihao nya.")
1330 --addToActionQueue(0,SKILL_EVENT_BATTLE_SYSTEM_MESSAGE,0x02,0,0)
1331 end
1332 if (onPress("R") and inBattle) then
1333 --function addToActionQueue(slot,cmd,spell,target,cost)
1334 --addToActionQueue(0,SKILL_LORE,0x03,0,0)
1335 end
1336 -- check if we're running on ROTDS or BNW/Vanilla
1337 checkRomType()
1338 if (inBattle) then
1339 updateBattleState()
1340 end
1341end
1342
1343emu.registerbefore(main)
1344main()