Mega Man Battle Network 3: Implement New Game (#1198)
* Initializes MMBN3 world with empty files
* Adds item names to item dict
* Adds locations and names
* Adds skeleton of MMBN3Client. Mostly copy pasta from OOT
* Fixed some style and formatting
* More incremental Lua tests
* Adds all locations and checking to Lua connector
* Made class definitions for TextPet Parser
* Begun connecting item delivery system through lua and textpet
* Lua Connection can now send test items
* Item Delivery is now parameterized. Test command can send any chip
* Adds the ability to send non-chip items
* Fixes name errors in python client
* Fixes count for zenny, attempts to fix bugfrags
* Fixes an issue where you always received 255 bugfrags
* Converts zenny and bugfrag amounts to little endian bytecode
* Checks game state before sending chips
Adds debug option to display information overlayed on rom
Fixes chip indexing issue for chips with ids over 255
Minor text fixes
* Adds in some animation reset instructions during item get message
* Stores previously collected item index in save, re-sends missing items
* Adds title screen check before sending locations
Loading items from save could not be done via RAM. Had to be added in
assembly
* Adds progressive undernet check
* Added library for lzss decoding bits of rom
* More progress on parsing text events from ROM
* Adds a way to inject messages into ScriptArchive data structure and generate bytecode
* Adds Item definitions, passes to client
* Adds regions and item collection rules
* Touched up a few names and values that have changed in preparation for the final patching
* Modifying messages via item is now successful
* Added generate_output hook to generate ROM data
* Generates ROM successfully
* Fixes navi cust give index
* Whoops forgot to wrap this in brackets
* Injects extra scripts for undernet rankings
* Programs had ammount and color swapped
* Prompts the user for their username when connecting
* Adds flagClear to the list of commands to avoid overwriting
* Fixes message box crashes and several other multiworld issues
* Fixes IDs and names of several items and locations
* Added .gba to gitignore
* Fixes compatibility after recent rebase
* Fixes some locations and items that are otherwise unobtainable
* Attempts to make a working launcher in the installer
* Creates installer and fixes several inaccessible locations
* Many minor changes to items, locations, and requirements made during testing
* Adds an info page for MMBN3
* Fixes failing tests by removing duplicate IDs and properly marking progression items
* Accidentally forgot to un-remove the thing
* Whoops, changed this by accident
* Updates self.world references to self.multiworld
* Fixes imports to use from imports instead of using the namespace
* Removed some leftover merge artifacts from inno setup
* Puts back that darned signtool line again
* Adds Overworld Metro keys as items
* Adds TamaCode and puts shortcuts behind cyber passes
* Fixes Numberman code 16 check
* Fixes metro access logic and adds text to metro
* Reworks Lua to fix crashing when many items are queued
* Items for other BN3 games for different players are no longer given in the main player's ROM as well
* Fixes incorrect Item ID for ACDC Metro
* Fixes multi-box text messages
* Adds timer before sending an item
* Forgot to remove the second box of SubMems
* Updates patch and lua to prevent softlocks and crashes
* Adds options for extra undernet ranks, exclude jobs
* Extra GigFreez now gives 20 bugfrags
* Additional Progressive Undernets can no longer appear on the WWW Base
* Moves item signal byte to empty area of flags instead of end of RAM
* Adds Chocolate Shop locations and navi chips to fill them
* Fixes save crash, and added chocolates to lua
* Fixes chocolate stand selling out text, removes DrillMan cube in Undernet
* Replaces old messaging system with direct memory manipulation for receiving items
* Removes NDSPY requirements from MMBN3 by manually adapting the GBA's lz10 algorithm
* Fixes the names of Hospital-1 Locations
* Adds Canary Bit to avoid sending checks when title screen check fails
* Gaining a cybermetro pass will now open the shortcut immediately
* Randomizes the two accessible areas of Undernet 7, adds Hammer as item
* Adds new locations to connector lua
* Injects the name of the item into trade quests
* Fixes copy-paste error in docs
* Fixes merge artifacts and depracated code
* Nut-wafer stand now faces Lan the right way after buying
* Removes unused Goal Option and updates the readme to include most recent changes
* Touch-ups and formatting changes
* The Great Fillerization update. Dozens of items changed to Filler
* Replaces instances of Mega Man with MegaMan
* Update worlds/mmbn3/docs/en_MegaMan Battle Network 3.md
Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
* Update worlds/mmbn3/__init__.py
Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
* Apply suggestions from code review
Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com>
* Changes code ordering to suit base class's
* assert_generate now checks for roms. Minor text fixes
* Makes player specific frequency and excluded location options
* Apply suggestions from code review
Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
* Addresses suggested changes from PR review
* Replaces ndspy lz10 with MIT-compliant nlzss lz10
* apworld compatibility fix for mmbn3_options from utils
* Addressing more comments by el-u
* APworld will now pull patch from zip folder
* Apply suggestions from code review
Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
* Cleaned up comments for progressive undernet ROM function, moved index list to field to avoid re-initializing
* Removes improper player-indexed location/item dicts, replaces with world member variables
* Avoids redefining list in progressive undernet ROM function
* Filler items can no longer be generated beyond their specified amounts
* Fixes list copying issue with item frequencies
* Adds BN3 Client Generation back into Launcher settings
* Fixes typos causing huge problems
* Fixed non-relative import for apworld
* Removes custom enum implementation that broke pickle
* Displays message when attempting to load an incorrect ROM, will not attempt to patch it
* Filler items can now only be placed once
* Changes path in setup doc to match Lua path changes
* Fixes file extension for MMBN3 file
* Replaces magic number with reference to value in NetUtils
* Moves victory rules to set_rules. Removes commented out code
* Rewrites Lua script to send block of memory
* Fixes off-by-one error in sending bytes for locations
* Fixes issue with invalid characters in text parsing, and WWW monitor text box parsing
* Moves trade text injection to init so it has access to options
* Attempts to split the text boxes for hinted items
* Trade checks now provide hints if the option is set for them
* Fixes escape character issue for BizHawk 2.9.1
Something in Bizhawk lua parsing changed to dislike the escaped tilde.
I'm not even entirely sure why it was escaped in the first place, but
this should fix the compatibility of it.
* Re-adds desk check that it turns out actually does exist
* Updates requirements to mention bizhawk 2.7 instead of 2.3.1
* Fixes off-by-one error in command byte counts
* Fixes program color indices
* Fixes newline PEP violations
* Reverts an accidental whitespace change made to launcher.py
* Fixes URL formatting on link to settings from setup guide
Co-authored-by: Zach Parks <zach@alliware.com>
* Splits several lines in the readme to avoid excessive length
* Fixes formatting and (hopefully) reduces cringe of joke in setup doc
* Removes unnecessary constructor
* Changes item frequency generation to avoid reusing the same references
Co-authored-by: Zach Parks <zach@alliware.com>
---------
Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com>
Co-authored-by: Zach Parks <zach@alliware.com>
2023-06-29 18:36:01 +00:00
|
|
|
|
local socket = require("socket")
|
|
|
|
|
local json = require('json')
|
|
|
|
|
local math = require('math')
|
|
|
|
|
require('common')
|
|
|
|
|
|
|
|
|
|
local last_modified_date = '2023-31-05' -- Should be the last modified date
|
|
|
|
|
local script_version = 4
|
|
|
|
|
|
|
|
|
|
local bizhawk_version = client.getversion()
|
|
|
|
|
local bizhawk_major, bizhawk_minor, bizhawk_patch = bizhawk_version:match("(%d+)%.(%d+)%.?(%d*)")
|
|
|
|
|
bizhawk_major = tonumber(bizhawk_major)
|
|
|
|
|
bizhawk_minor = tonumber(bizhawk_minor)
|
|
|
|
|
if bizhawk_patch == "" then
|
|
|
|
|
bizhawk_patch = 0
|
|
|
|
|
else
|
|
|
|
|
bizhawk_patch = tonumber(bizhawk_patch)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local STATE_OK = "Ok"
|
|
|
|
|
local STATE_TENTATIVELY_CONNECTED = "Tentatively Connected"
|
|
|
|
|
local STATE_INITIAL_CONNECTION_MADE = "Initial Connection Made"
|
|
|
|
|
local STATE_UNINITIALIZED = "Uninitialized"
|
|
|
|
|
|
|
|
|
|
local prevstate = ""
|
|
|
|
|
local curstate = STATE_UNINITIALIZED
|
|
|
|
|
local mmbn3Socket = nil
|
|
|
|
|
local frame = 0
|
|
|
|
|
|
|
|
|
|
-- States
|
|
|
|
|
local ITEMSTATE_NONINITIALIZED = "Game Not Yet Started" -- Game has not yet started
|
|
|
|
|
local ITEMSTATE_NONITEM = "Non-Itemable State" -- Do not send item now. RAM is not capable of holding
|
|
|
|
|
local ITEMSTATE_IDLE = "Item State Ready" -- Ready for the next item if there are any
|
|
|
|
|
local ITEMSTATE_SENT = "Item Sent Not Claimed" -- The ItemBit is set, but the dialog has not been closed yet
|
|
|
|
|
local itemState = ITEMSTATE_NONINITIALIZED
|
|
|
|
|
|
|
|
|
|
local itemQueued = nil
|
|
|
|
|
local itemQueueCounter = 120
|
|
|
|
|
|
|
|
|
|
local debugEnabled = false
|
|
|
|
|
local game_complete = false
|
|
|
|
|
|
|
|
|
|
local backup_bytes = nil
|
|
|
|
|
|
|
|
|
|
local itemsReceived = {}
|
|
|
|
|
local previousMessageBit = 0x00
|
|
|
|
|
|
|
|
|
|
local key_item_start_address = 0x20019C0
|
|
|
|
|
|
|
|
|
|
-- The Canary Byte is a flag byte that is intentionally left unused. If this byte is FF, then we know the flag
|
|
|
|
|
-- data cannot be trusted, so we don't send checks.
|
|
|
|
|
local canary_byte = 0x20001A9
|
|
|
|
|
|
|
|
|
|
local charDict = {
|
|
|
|
|
[' ']=0x00,['0']=0x01,['1']=0x02,['2']=0x03,['3']=0x04,['4']=0x05,['5']=0x06,['6']=0x07,['7']=0x08,['8']=0x09,['9']=0x0A,
|
|
|
|
|
['A']=0x0B,['B']=0x0C,['C']=0x0D,['D']=0x0E,['E']=0x0F,['F']=0x10,['G']=0x11,['H']=0x12,['I']=0x13,['J']=0x14,['K']=0x15,
|
|
|
|
|
['L']=0x16,['M']=0x17,['N']=0x18,['O']=0x19,['P']=0x1A,['Q']=0x1B,['R']=0x1C,['S']=0x1D,['T']=0x1E,['U']=0x1F,['V']=0x20,
|
|
|
|
|
['W']=0x21,['X']=0x22,['Y']=0x23,['Z']=0x24,['a']=0x25,['b']=0x26,['c']=0x27,['d']=0x28,['e']=0x29,['f']=0x2A,['g']=0x2B,
|
|
|
|
|
['h']=0x2C,['i']=0x2D,['j']=0x2E,['k']=0x2F,['l']=0x30,['m']=0x31,['n']=0x32,['o']=0x33,['p']=0x34,['q']=0x35,['r']=0x36,
|
|
|
|
|
['s']=0x37,['t']=0x38,['u']=0x39,['v']=0x3A,['w']=0x3B,['x']=0x3C,['y']=0x3D,['z']=0x3E,['-']=0x3F,['×']=0x40,[']=']=0x41,
|
|
|
|
|
[':']=0x42,['+']=0x43,['÷']=0x44,['※']=0x45,['*']=0x46,['!']=0x47,['?']=0x48,['%']=0x49,['&']=0x4A,[',']=0x4B,['⋯']=0x4C,
|
|
|
|
|
['.']=0x4D,['・']=0x4E,[';']=0x4F,['\'']=0x50,['\"']=0x51,['~']=0x52,['/']=0x53,['(']=0x54,[')']=0x55,['「']=0x56,['」']=0x57,
|
|
|
|
|
["[V2]"]=0x58,["[V3]"]=0x59,["[V4]"]=0x5A,["[V5]"]=0x5B,['@']=0x5C,['♥']=0x5D,['♪']=0x5E,["[MB]"]=0x5F,['■']=0x60,['_']=0x61,
|
|
|
|
|
["[circle1]"]=0x62,["[circle2]"]=0x63,["[cross1]"]=0x64,["[cross2]"]=0x65,["[bracket1]"]=0x66,["[bracket2]"]=0x67,["[ModTools1]"]=0x68,
|
|
|
|
|
["[ModTools2]"]=0x69,["[ModTools3]"]=0x6A,['Σ']=0x6B,['Ω']=0x6C,['α']=0x6D,['β']=0x6E,['#']=0x6F,['…']=0x70,['>']=0x71,
|
|
|
|
|
['<']=0x72,['エ']=0x73,["[BowneGlobal1]"]=0x74,["[BowneGlobal2]"]=0x75,["[BowneGlobal3]"]=0x76,["[BowneGlobal4]"]=0x77,
|
|
|
|
|
["[BowneGlobal5]"]=0x78,["[BowneGlobal6]"]=0x79,["[BowneGlobal7]"]=0x7A,["[BowneGlobal8]"]=0x7B,["[BowneGlobal9]"]=0x7C,
|
|
|
|
|
["[BowneGlobal10]"]=0x7D,["[BowneGlobal11]"]=0x7E,['\n']=0xE8
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
local TableConcat = function(t1,t2)
|
|
|
|
|
for i=1,#t2 do
|
|
|
|
|
t1[#t1+1] = t2[i]
|
|
|
|
|
end
|
|
|
|
|
return t1
|
|
|
|
|
end
|
|
|
|
|
local int32ToByteList_le = function(x)
|
|
|
|
|
bytes = {}
|
|
|
|
|
hexString = string.format("%08x", x)
|
|
|
|
|
for i=#hexString, 1, -2 do
|
|
|
|
|
hbyte = hexString:sub(i-1, i)
|
|
|
|
|
table.insert(bytes,tonumber(hbyte,16))
|
|
|
|
|
end
|
|
|
|
|
return bytes
|
|
|
|
|
end
|
|
|
|
|
local int16ToByteList_le = function(x)
|
|
|
|
|
bytes = {}
|
|
|
|
|
hexString = string.format("%04x", x)
|
|
|
|
|
for i=#hexString, 1, -2 do
|
|
|
|
|
hbyte = hexString:sub(i-1, i)
|
|
|
|
|
table.insert(bytes,tonumber(hbyte,16))
|
|
|
|
|
end
|
|
|
|
|
return bytes
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local IsInMenu = function()
|
|
|
|
|
return bit.band(memory.read_u8(0x0200027A),0x10) ~= 0
|
|
|
|
|
end
|
|
|
|
|
local IsInTransition = function()
|
|
|
|
|
return bit.band(memory.read_u8(0x02001880), 0x10) ~= 0
|
|
|
|
|
end
|
|
|
|
|
local IsInDialog = function()
|
|
|
|
|
return bit.band(memory.read_u8(0x02009480),0x01) ~= 0
|
|
|
|
|
end
|
|
|
|
|
local IsInBattle = function()
|
|
|
|
|
return memory.read_u8(0x020097F8) == 0x08
|
|
|
|
|
end
|
|
|
|
|
local IsItemQueued = function()
|
|
|
|
|
return memory.read_u8(0x2000224) == 0x01
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- This function actually determines when you're on ANY full-screen menu (navi cust, link battle, etc.) but we
|
|
|
|
|
-- don't want to check any locations there either so it's fine.
|
|
|
|
|
local IsOnTitle = function()
|
|
|
|
|
return bit.band(memory.read_u8(0x020097F8),0x04) == 0
|
|
|
|
|
end
|
|
|
|
|
local IsItemable = function()
|
|
|
|
|
return not IsInMenu() and not IsInTransition() and not IsInDialog() and not IsInBattle() and not IsOnTitle() and not IsItemQueued()
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local is_game_complete = function()
|
|
|
|
|
if IsOnTitle() or itemState == ITEMSTATE_NONINITIALIZED then return game_complete end
|
|
|
|
|
|
|
|
|
|
-- If the game is already marked complete, do not read memory
|
|
|
|
|
if game_complete then return true end
|
|
|
|
|
local is_alpha_defeated = bit.band(memory.read_u8(0x2000433), 0x01) ~= 0
|
|
|
|
|
|
|
|
|
|
if (is_alpha_defeated) then
|
|
|
|
|
game_complete = true
|
|
|
|
|
return true
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Game is still ongoing
|
|
|
|
|
return false
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local saveItemIndexToRAM = function(newIndex)
|
|
|
|
|
memory.write_s16_le(0x20000AE,newIndex)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local loadItemIndexFromRAM = function()
|
|
|
|
|
last_index = memory.read_s16_le(0x20000AE)
|
|
|
|
|
if (last_index < 0) then
|
|
|
|
|
last_index = 0
|
|
|
|
|
saveItemIndexToRAM(0)
|
|
|
|
|
end
|
|
|
|
|
return last_index
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local loadPlayerNameFromROM = function()
|
|
|
|
|
return memory.read_bytes_as_array(0x7FFFC0,63,"ROM")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local check_all_locations = function()
|
|
|
|
|
local location_checks = {}
|
|
|
|
|
-- Title Screen should not check items
|
|
|
|
|
if itemState == ITEMSTATE_NONINITIALIZED or IsInTransition() then
|
|
|
|
|
return location_checks
|
|
|
|
|
end
|
|
|
|
|
if memory.read_u8(canary_byte) == 0xFF then
|
|
|
|
|
return location_checks
|
|
|
|
|
end
|
|
|
|
|
for k,v in pairs(memory.read_bytes_as_dict(0x02000000, 0x434)) do
|
|
|
|
|
str_k = string.format("%x", k)
|
|
|
|
|
location_checks[str_k] = v
|
|
|
|
|
end
|
|
|
|
|
return location_checks
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local Check_Progressive_Undernet_ID = function()
|
|
|
|
|
ordered_offsets = { 0x020019DB,0x020019DC,0x020019DD,0x020019DE,0x020019DF,0x020019E0,0x020019FA,0x020019E2 }
|
|
|
|
|
for i=1,#ordered_offsets do
|
|
|
|
|
offset=ordered_offsets[i]
|
|
|
|
|
|
|
|
|
|
if memory.read_u8(offset) == 0 then
|
|
|
|
|
return i
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
return 9
|
|
|
|
|
end
|
|
|
|
|
local GenerateTextBytes = function(message)
|
|
|
|
|
bytes = {}
|
|
|
|
|
for i = 1, #message do
|
|
|
|
|
local c = message:sub(i,i)
|
|
|
|
|
table.insert(bytes, charDict[c])
|
|
|
|
|
end
|
|
|
|
|
return bytes
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Item Message Generation functions
|
|
|
|
|
local Next_Progressive_Undernet_ID = function(index)
|
|
|
|
|
ordered_IDs = { 27,28,29,30,31,32,58,34}
|
|
|
|
|
if index > #ordered_IDs then
|
|
|
|
|
--It shouldn't reach this point, but if it does, just give another GigFreez I guess
|
|
|
|
|
return 34
|
|
|
|
|
end
|
|
|
|
|
item_index=ordered_IDs[index]
|
|
|
|
|
return item_index
|
|
|
|
|
end
|
|
|
|
|
local Extra_Progressive_Undernet = function()
|
|
|
|
|
fragBytes = int32ToByteList_le(20)
|
|
|
|
|
bytes = {
|
|
|
|
|
0xF6, 0x50, fragBytes[1], fragBytes[2], fragBytes[3], fragBytes[4], 0xFF, 0xFF, 0xFF
|
|
|
|
|
}
|
|
|
|
|
bytes = TableConcat(bytes, GenerateTextBytes("The extra data\ndecompiles into:\n\"20 BugFrags\"!!"))
|
|
|
|
|
return bytes
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local GenerateChipGet = function(chip, code, amt)
|
|
|
|
|
chipBytes = int16ToByteList_le(chip)
|
|
|
|
|
bytes = {
|
|
|
|
|
0xF6, 0x10, chipBytes[1], chipBytes[2], code, amt,
|
|
|
|
|
charDict['G'], charDict['o'], charDict['t'], charDict[' '], charDict['a'], charDict[' '], charDict['c'], charDict['h'], charDict['i'], charDict['p'], charDict[' '], charDict['f'], charDict['o'], charDict['r'], charDict['\n'],
|
|
|
|
|
|
|
|
|
|
}
|
|
|
|
|
if chip < 256 then
|
|
|
|
|
bytes = TableConcat(bytes, {
|
|
|
|
|
charDict['\"'], 0xF9,0x00,chipBytes[1],0x01,0x00,0xF9,0x00,code,0x03, charDict['\"'],charDict['!'],charDict['!']
|
|
|
|
|
})
|
|
|
|
|
else
|
|
|
|
|
bytes = TableConcat(bytes, {
|
|
|
|
|
charDict['\"'], 0xF9,0x00,chipBytes[1],0x02,0x00,0xF9,0x00,code,0x03, charDict['\"'],charDict['!'],charDict['!']
|
|
|
|
|
})
|
|
|
|
|
end
|
|
|
|
|
return bytes
|
|
|
|
|
end
|
|
|
|
|
local GenerateKeyItemGet = function(item, amt)
|
|
|
|
|
bytes = {
|
|
|
|
|
0xF6, 0x00, item, amt,
|
|
|
|
|
charDict['G'], charDict['o'], charDict['t'], charDict[' '], charDict['a'], charDict['\n'],
|
|
|
|
|
charDict['\"'], 0xF9, 0x00, item, 0x00, charDict['\"'],charDict['!'],charDict['!']
|
|
|
|
|
}
|
|
|
|
|
return bytes
|
|
|
|
|
end
|
|
|
|
|
local GenerateSubChipGet = function(subchip, amt)
|
|
|
|
|
-- SubChips have an extra bit of trouble. If you have too many, they're supposed to skip to another text bank that doesn't give you the item
|
|
|
|
|
-- Instead, I'm going to just let it get eaten
|
|
|
|
|
bytes = {
|
|
|
|
|
0xF6, 0x20, subchip, amt, 0xFF, 0xFF, 0xFF,
|
|
|
|
|
charDict['G'], charDict['o'], charDict['t'], charDict[' '], charDict['a'], charDict['\n'],
|
|
|
|
|
charDict['S'], charDict['u'], charDict['b'], charDict['C'], charDict['h'], charDict['i'], charDict['p'], charDict[' '], charDict['f'], charDict['o'], charDict['r'], charDict['\n'],
|
|
|
|
|
charDict['\"'], 0xF9, 0x00, subchip, 0x00, charDict['\"'],charDict['!'],charDict['!']
|
|
|
|
|
}
|
|
|
|
|
return bytes
|
|
|
|
|
end
|
|
|
|
|
local GenerateZennyGet = function(amt)
|
|
|
|
|
zennyBytes = int32ToByteList_le(amt)
|
|
|
|
|
bytes = {
|
|
|
|
|
0xF6, 0x30, zennyBytes[1], zennyBytes[2], zennyBytes[3], zennyBytes[4], 0xFF, 0xFF, 0xFF,
|
|
|
|
|
charDict['G'], charDict['o'], charDict['t'], charDict[' '], charDict['a'], charDict['\n'], charDict['\"']
|
|
|
|
|
}
|
|
|
|
|
-- The text needs to be added one char at a time, so we need to convert the number to a string then iterate through it
|
|
|
|
|
zennyStr = tostring(amt)
|
|
|
|
|
for i = 1, #zennyStr do
|
|
|
|
|
local c = zennyStr:sub(i,i)
|
|
|
|
|
table.insert(bytes, charDict[c])
|
|
|
|
|
end
|
|
|
|
|
bytes = TableConcat(bytes, {
|
|
|
|
|
charDict[' '], charDict['Z'], charDict['e'], charDict['n'], charDict['n'], charDict['y'], charDict['s'], charDict['\"'],charDict['!'],charDict['!']
|
|
|
|
|
})
|
|
|
|
|
return bytes
|
|
|
|
|
end
|
|
|
|
|
local GenerateProgramGet = function(program, color, amt)
|
|
|
|
|
bytes = {
|
|
|
|
|
0xF6, 0x40, (program * 4), amt, color,
|
|
|
|
|
charDict['G'], charDict['o'], charDict['t'], charDict[' '], charDict['a'], charDict[' '], charDict['N'], charDict['a'], charDict['v'], charDict['i'], charDict['\n'],
|
|
|
|
|
charDict['C'], charDict['u'], charDict['s'], charDict['t'], charDict['o'], charDict['m'], charDict['i'], charDict['z'], charDict['e'], charDict['r'], charDict[' '], charDict['P'], charDict['r'], charDict['o'], charDict['g'], charDict['r'], charDict['a'], charDict['m'], charDict[':'], charDict['\n'],
|
|
|
|
|
charDict['\"'], 0xF9, 0x00, program, 0x05, charDict['\"'],charDict['!'],charDict['!']
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return bytes
|
|
|
|
|
end
|
|
|
|
|
local GenerateBugfragGet = function(amt)
|
|
|
|
|
fragBytes = int32ToByteList_le(amt)
|
|
|
|
|
bytes = {
|
|
|
|
|
0xF6, 0x50, fragBytes[1], fragBytes[2], fragBytes[3], fragBytes[4], 0xFF, 0xFF, 0xFF,
|
|
|
|
|
charDict['G'], charDict['o'], charDict['t'], charDict[':'], charDict['\n'], charDict['\"']
|
|
|
|
|
}
|
|
|
|
|
-- The text needs to be added one char at a time, so we need to convert the number to a string then iterate through it
|
|
|
|
|
bugFragStr = tostring(amt)
|
|
|
|
|
for i = 1, #bugFragStr do
|
|
|
|
|
local c = bugFragStr:sub(i,i)
|
|
|
|
|
table.insert(bytes, charDict[c])
|
|
|
|
|
end
|
|
|
|
|
bytes = TableConcat(bytes, {
|
|
|
|
|
charDict[' '], charDict['B'], charDict['u'], charDict['g'], charDict['F'], charDict['r'], charDict['a'], charDict['g'], charDict['s'], charDict['\"'],charDict['!'],charDict['!']
|
|
|
|
|
})
|
|
|
|
|
return bytes
|
|
|
|
|
end
|
|
|
|
|
local GenerateGetMessageFromItem = function(item)
|
|
|
|
|
--Special case for progressive undernet
|
|
|
|
|
if item["type"] == "undernet" then
|
|
|
|
|
undernet_id = Check_Progressive_Undernet_ID()
|
|
|
|
|
if undernet_id > 8 then
|
|
|
|
|
return Extra_Progressive_Undernet()
|
|
|
|
|
end
|
|
|
|
|
return GenerateKeyItemGet(Next_Progressive_Undernet_ID(undernet_id),1)
|
|
|
|
|
elseif item["type"] == "chip" then
|
|
|
|
|
return GenerateChipGet(item["itemID"], item["subItemID"], item["count"])
|
|
|
|
|
elseif item["type"] == "key" then
|
|
|
|
|
return GenerateKeyItemGet(item["itemID"], item["count"])
|
|
|
|
|
elseif item["type"] == "subchip" then
|
|
|
|
|
return GenerateSubChipGet(item["itemID"], item["count"])
|
|
|
|
|
elseif item["type"] == "zenny" then
|
|
|
|
|
return GenerateZennyGet(item["count"])
|
|
|
|
|
elseif item["type"] == "program" then
|
|
|
|
|
return GenerateProgramGet(item["itemID"], item["subItemID"], item["count"])
|
|
|
|
|
elseif item["type"] == "bugfrag" then
|
|
|
|
|
return GenerateBugfragGet(item["count"])
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
return GenerateTextBytes("Empty Message")
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local GetMessage = function(item)
|
|
|
|
|
startBytes = {0x02, 0x00}
|
|
|
|
|
playerLockBytes = {0xF8,0x00, 0xF8, 0x10}
|
|
|
|
|
msgOpenBytes = {0xF1, 0x02}
|
|
|
|
|
textBytes = GenerateTextBytes("Receiving\ndata from\n"..item["sender"]..".")
|
|
|
|
|
dotdotWaitBytes = {0xEA,0x00,0x0A,0x00,0x4D,0xEA,0x00,0x0A,0x00,0x4D}
|
|
|
|
|
continueBytes = {0xEB, 0xE9}
|
|
|
|
|
-- continueBytes = {0xE9}
|
|
|
|
|
playReceiveAnimationBytes = {0xF8,0x04,0x18}
|
|
|
|
|
chipGiveBytes = GenerateGetMessageFromItem(item)
|
|
|
|
|
playerFinishBytes = {0xF8, 0x0C}
|
|
|
|
|
playerUnlockBytes={0xEB, 0xF8, 0x08}
|
|
|
|
|
-- playerUnlockBytes={0xF8, 0x08}
|
|
|
|
|
endMessageBytes = {0xF8, 0x10, 0xE7}
|
|
|
|
|
|
|
|
|
|
bytes = {}
|
|
|
|
|
bytes = TableConcat(bytes,startBytes)
|
|
|
|
|
bytes = TableConcat(bytes,playerLockBytes)
|
|
|
|
|
bytes = TableConcat(bytes,msgOpenBytes)
|
|
|
|
|
bytes = TableConcat(bytes,textBytes)
|
|
|
|
|
bytes = TableConcat(bytes,dotdotWaitBytes)
|
|
|
|
|
bytes = TableConcat(bytes,continueBytes)
|
|
|
|
|
bytes = TableConcat(bytes,playReceiveAnimationBytes)
|
|
|
|
|
bytes = TableConcat(bytes,chipGiveBytes)
|
|
|
|
|
bytes = TableConcat(bytes,playerFinishBytes)
|
|
|
|
|
bytes = TableConcat(bytes,playerUnlockBytes)
|
|
|
|
|
bytes = TableConcat(bytes,endMessageBytes)
|
|
|
|
|
return bytes
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local getChipCodeIndex = function(chip_id, chip_code)
|
|
|
|
|
chipCodeArrayStartAddress = 0x8011510 + (0x20 * chip_id)
|
|
|
|
|
for i=1,6 do
|
|
|
|
|
currentCode = memory.read_u8(chipCodeArrayStartAddress + (i-1))
|
|
|
|
|
if currentCode == chip_code then
|
|
|
|
|
return i-1
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
return 0
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local getProgramColorIndex = function(program_id, program_color)
|
|
|
|
|
-- The general case, most programs use white pink or yellow. This is the values the enums already have
|
2023-06-30 22:34:53 +00:00
|
|
|
|
if program_id >= 20 and program_id <= 47 then
|
Mega Man Battle Network 3: Implement New Game (#1198)
* Initializes MMBN3 world with empty files
* Adds item names to item dict
* Adds locations and names
* Adds skeleton of MMBN3Client. Mostly copy pasta from OOT
* Fixed some style and formatting
* More incremental Lua tests
* Adds all locations and checking to Lua connector
* Made class definitions for TextPet Parser
* Begun connecting item delivery system through lua and textpet
* Lua Connection can now send test items
* Item Delivery is now parameterized. Test command can send any chip
* Adds the ability to send non-chip items
* Fixes name errors in python client
* Fixes count for zenny, attempts to fix bugfrags
* Fixes an issue where you always received 255 bugfrags
* Converts zenny and bugfrag amounts to little endian bytecode
* Checks game state before sending chips
Adds debug option to display information overlayed on rom
Fixes chip indexing issue for chips with ids over 255
Minor text fixes
* Adds in some animation reset instructions during item get message
* Stores previously collected item index in save, re-sends missing items
* Adds title screen check before sending locations
Loading items from save could not be done via RAM. Had to be added in
assembly
* Adds progressive undernet check
* Added library for lzss decoding bits of rom
* More progress on parsing text events from ROM
* Adds a way to inject messages into ScriptArchive data structure and generate bytecode
* Adds Item definitions, passes to client
* Adds regions and item collection rules
* Touched up a few names and values that have changed in preparation for the final patching
* Modifying messages via item is now successful
* Added generate_output hook to generate ROM data
* Generates ROM successfully
* Fixes navi cust give index
* Whoops forgot to wrap this in brackets
* Injects extra scripts for undernet rankings
* Programs had ammount and color swapped
* Prompts the user for their username when connecting
* Adds flagClear to the list of commands to avoid overwriting
* Fixes message box crashes and several other multiworld issues
* Fixes IDs and names of several items and locations
* Added .gba to gitignore
* Fixes compatibility after recent rebase
* Fixes some locations and items that are otherwise unobtainable
* Attempts to make a working launcher in the installer
* Creates installer and fixes several inaccessible locations
* Many minor changes to items, locations, and requirements made during testing
* Adds an info page for MMBN3
* Fixes failing tests by removing duplicate IDs and properly marking progression items
* Accidentally forgot to un-remove the thing
* Whoops, changed this by accident
* Updates self.world references to self.multiworld
* Fixes imports to use from imports instead of using the namespace
* Removed some leftover merge artifacts from inno setup
* Puts back that darned signtool line again
* Adds Overworld Metro keys as items
* Adds TamaCode and puts shortcuts behind cyber passes
* Fixes Numberman code 16 check
* Fixes metro access logic and adds text to metro
* Reworks Lua to fix crashing when many items are queued
* Items for other BN3 games for different players are no longer given in the main player's ROM as well
* Fixes incorrect Item ID for ACDC Metro
* Fixes multi-box text messages
* Adds timer before sending an item
* Forgot to remove the second box of SubMems
* Updates patch and lua to prevent softlocks and crashes
* Adds options for extra undernet ranks, exclude jobs
* Extra GigFreez now gives 20 bugfrags
* Additional Progressive Undernets can no longer appear on the WWW Base
* Moves item signal byte to empty area of flags instead of end of RAM
* Adds Chocolate Shop locations and navi chips to fill them
* Fixes save crash, and added chocolates to lua
* Fixes chocolate stand selling out text, removes DrillMan cube in Undernet
* Replaces old messaging system with direct memory manipulation for receiving items
* Removes NDSPY requirements from MMBN3 by manually adapting the GBA's lz10 algorithm
* Fixes the names of Hospital-1 Locations
* Adds Canary Bit to avoid sending checks when title screen check fails
* Gaining a cybermetro pass will now open the shortcut immediately
* Randomizes the two accessible areas of Undernet 7, adds Hammer as item
* Adds new locations to connector lua
* Injects the name of the item into trade quests
* Fixes copy-paste error in docs
* Fixes merge artifacts and depracated code
* Nut-wafer stand now faces Lan the right way after buying
* Removes unused Goal Option and updates the readme to include most recent changes
* Touch-ups and formatting changes
* The Great Fillerization update. Dozens of items changed to Filler
* Replaces instances of Mega Man with MegaMan
* Update worlds/mmbn3/docs/en_MegaMan Battle Network 3.md
Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
* Update worlds/mmbn3/__init__.py
Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
* Apply suggestions from code review
Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com>
* Changes code ordering to suit base class's
* assert_generate now checks for roms. Minor text fixes
* Makes player specific frequency and excluded location options
* Apply suggestions from code review
Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
* Addresses suggested changes from PR review
* Replaces ndspy lz10 with MIT-compliant nlzss lz10
* apworld compatibility fix for mmbn3_options from utils
* Addressing more comments by el-u
* APworld will now pull patch from zip folder
* Apply suggestions from code review
Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
* Cleaned up comments for progressive undernet ROM function, moved index list to field to avoid re-initializing
* Removes improper player-indexed location/item dicts, replaces with world member variables
* Avoids redefining list in progressive undernet ROM function
* Filler items can no longer be generated beyond their specified amounts
* Fixes list copying issue with item frequencies
* Adds BN3 Client Generation back into Launcher settings
* Fixes typos causing huge problems
* Fixed non-relative import for apworld
* Removes custom enum implementation that broke pickle
* Displays message when attempting to load an incorrect ROM, will not attempt to patch it
* Filler items can now only be placed once
* Changes path in setup doc to match Lua path changes
* Fixes file extension for MMBN3 file
* Replaces magic number with reference to value in NetUtils
* Moves victory rules to set_rules. Removes commented out code
* Rewrites Lua script to send block of memory
* Fixes off-by-one error in sending bytes for locations
* Fixes issue with invalid characters in text parsing, and WWW monitor text box parsing
* Moves trade text injection to init so it has access to options
* Attempts to split the text boxes for hinted items
* Trade checks now provide hints if the option is set for them
* Fixes escape character issue for BizHawk 2.9.1
Something in Bizhawk lua parsing changed to dislike the escaped tilde.
I'm not even entirely sure why it was escaped in the first place, but
this should fix the compatibility of it.
* Re-adds desk check that it turns out actually does exist
* Updates requirements to mention bizhawk 2.7 instead of 2.3.1
* Fixes off-by-one error in command byte counts
* Fixes program color indices
* Fixes newline PEP violations
* Reverts an accidental whitespace change made to launcher.py
* Fixes URL formatting on link to settings from setup guide
Co-authored-by: Zach Parks <zach@alliware.com>
* Splits several lines in the readme to avoid excessive length
* Fixes formatting and (hopefully) reduces cringe of joke in setup doc
* Removes unnecessary constructor
* Changes item frequency generation to avoid reusing the same references
Co-authored-by: Zach Parks <zach@alliware.com>
---------
Co-authored-by: el-u <109771707+el-u@users.noreply.github.com>
Co-authored-by: SoldierofOrder <107806872+SoldierofOrder@users.noreply.github.com>
Co-authored-by: Zach Parks <zach@alliware.com>
2023-06-29 18:36:01 +00:00
|
|
|
|
return program_color-1
|
|
|
|
|
end
|
|
|
|
|
--The final three programs only have a color index 0, so just return those
|
|
|
|
|
if program_id > 47 then
|
|
|
|
|
return 0
|
|
|
|
|
end
|
|
|
|
|
--BrakChrg as an AP item only comes in orange, index 0
|
|
|
|
|
if program_id == 3 then
|
|
|
|
|
return 0
|
|
|
|
|
end
|
|
|
|
|
-- every other AP obtainable program returns only color index 3
|
|
|
|
|
return 3
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local addChip = function(chip_id, chip_code, amount)
|
|
|
|
|
chipStartAddress = 0x02001F60
|
|
|
|
|
chipOffset = 0x12 * chip_id
|
|
|
|
|
chip_code_index = getChipCodeIndex(chip_id, chip_code)
|
|
|
|
|
currentChipAddress = chipStartAddress + chipOffset + chip_code_index
|
|
|
|
|
currentChipCount = memory.read_u8(currentChipAddress)
|
|
|
|
|
memory.write_u8(currentChipAddress,currentChipCount+amount)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local addProgram = function(program_id, program_color, amount)
|
|
|
|
|
programStartAddress = 0x02001A80
|
|
|
|
|
programOffset = 0x04 * program_id
|
|
|
|
|
program_code_index = getProgramColorIndex(program_id, program_color)
|
|
|
|
|
currentProgramAddress = programStartAddress + programOffset + program_code_index
|
|
|
|
|
currentProgramCount = memory.read_u8(currentProgramAddress)
|
|
|
|
|
memory.write_u8(currentProgramAddress, currentProgramCount+amount)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local addSubChip = function(subchip_id, amount)
|
|
|
|
|
subChipStartAddress = 0x02001A30
|
|
|
|
|
--SubChip indices start after the key items, so subtract 112 from the index to get the actual subchip index
|
|
|
|
|
currentSubChipAddress = subChipStartAddress + (subchip_id - 112)
|
|
|
|
|
currentSubChipCount = memory.read_u8(currentSubChipAddress)
|
|
|
|
|
--TODO check submem, reject if number too big
|
|
|
|
|
memory.write_u8(currentSubChipAddress, currentSubChipCount+amount)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local changeZenny = function(val)
|
|
|
|
|
if val == nil then
|
|
|
|
|
return 0
|
|
|
|
|
end
|
|
|
|
|
if memory.read_u32_le(0x20018F4) <= math.abs(tonumber(val)) and tonumber(val) < 0 then
|
|
|
|
|
memory.write_u32_le(0x20018f4, 0)
|
|
|
|
|
val = 0
|
|
|
|
|
return "empty"
|
|
|
|
|
end
|
|
|
|
|
memory.write_u32_le(0x20018f4, memory.read_u32_le(0x20018F4) + tonumber(val))
|
|
|
|
|
if memory.read_u32_le(0x20018F4) > 999999 then
|
|
|
|
|
memory.write_u32_le(0x20018F4, 999999)
|
|
|
|
|
end
|
|
|
|
|
return val
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local changeFrags = function(val)
|
|
|
|
|
if val == nil then
|
|
|
|
|
return 0
|
|
|
|
|
end
|
|
|
|
|
if memory.read_u16_le(0x20018F8) <= math.abs(tonumber(val)) and tonumber(val) < 0 then
|
|
|
|
|
memory.write_u16_le(0x20018f8, 0)
|
|
|
|
|
val = 0
|
|
|
|
|
return "empty"
|
|
|
|
|
end
|
|
|
|
|
memory.write_u16_le(0x20018f8, memory.read_u16_le(0x20018F8) + tonumber(val))
|
|
|
|
|
if memory.read_u16_le(0x20018F8) > 9999 then
|
|
|
|
|
memory.write_u16_le(0x20018F8, 9999)
|
|
|
|
|
end
|
|
|
|
|
return val
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Fix Health Pools
|
|
|
|
|
local fix_hp = function()
|
|
|
|
|
-- Current Health fix
|
|
|
|
|
if IsInBattle() and not (memory.read_u16_le(0x20018A0) == memory.read_u16_le(0x2037294)) then
|
|
|
|
|
memory.write_u16_le(0x20018A0, memory.read_u16_le(0x2037294))
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Max Health Fix
|
|
|
|
|
if IsInBattle() and not (memory.read_u16_le(0x20018A2) == memory.read_u16_le(0x2037296)) then
|
|
|
|
|
memory.write_u16_le(0x20018A2, memory.read_u16_le(0x2037296))
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local changeRegMemory = function(amt)
|
|
|
|
|
regMemoryAddress = 0x02001897
|
|
|
|
|
currentRegMem = memory.read_u8(regMemoryAddress)
|
|
|
|
|
memory.write_u8(regMemoryAddress, currentRegMem + amt)
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local changeMaxHealth = function(val)
|
|
|
|
|
fix_hp()
|
|
|
|
|
if val == nil then
|
|
|
|
|
fix_hp()
|
|
|
|
|
return 0
|
|
|
|
|
end
|
|
|
|
|
if math.abs(tonumber(val)) >= memory.read_u16_le(0x20018A2) and tonumber(val) < 0 then
|
|
|
|
|
memory.write_u16_le(0x20018A2, 0)
|
|
|
|
|
if IsInBattle() then
|
|
|
|
|
memory.write_u16_le(0x2037296, memory.read_u16_le(0x20018A2))
|
|
|
|
|
if memory.read_u16_le(0x2037296) >= memory.read_u16_le(0x20018A2) then
|
|
|
|
|
memory.write_u16_le(0x2037296, memory.read_u16_le(0x20018A2))
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
fix_hp()
|
|
|
|
|
return "lethal"
|
|
|
|
|
end
|
|
|
|
|
memory.write_u16_le(0x20018A2, memory.read_u16_le(0x20018A2) + tonumber(val))
|
|
|
|
|
if memory.read_u16_le(0x20018A2) > 9999 then
|
|
|
|
|
memory.write_u16_le(0x20018A2, 9999)
|
|
|
|
|
end
|
|
|
|
|
if IsInBattle() then
|
|
|
|
|
memory.write_u16_le(0x2037296, memory.read_u16_le(0x20018A2))
|
|
|
|
|
end
|
|
|
|
|
fix_hp()
|
|
|
|
|
return val
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local SendItem = function(item)
|
|
|
|
|
if item["type"] == "undernet" then
|
|
|
|
|
undernet_id = Check_Progressive_Undernet_ID()
|
|
|
|
|
if undernet_id > 8 then
|
|
|
|
|
-- Generate Extra BugFrags
|
|
|
|
|
changeFrags(20)
|
|
|
|
|
gui.addmessage("Receiving extra Undernet Rank from "..item["sender"]..", +20 BugFrags")
|
|
|
|
|
-- print("Receiving extra Undernet Rank from "..item["sender"]..", +20 BugFrags")
|
|
|
|
|
else
|
|
|
|
|
itemAddress = key_item_start_address + Next_Progressive_Undernet_ID(undernet_id)
|
|
|
|
|
|
|
|
|
|
itemCount = memory.read_u8(itemAddress)
|
|
|
|
|
itemCount = itemCount + item["count"]
|
|
|
|
|
memory.write_u8(itemAddress, itemCount)
|
|
|
|
|
gui.addmessage("Received Undernet Rank from player "..item["sender"])
|
|
|
|
|
-- print("Received Undernet Rank from player "..item["sender"])
|
|
|
|
|
end
|
|
|
|
|
elseif item["type"] == "chip" then
|
|
|
|
|
addChip(item["itemID"], item["subItemID"], item["count"])
|
|
|
|
|
gui.addmessage("Received Chip "..item["itemName"].." from player "..item["sender"])
|
|
|
|
|
-- print("Received Chip "..item["itemName"].." from player "..item["sender"])
|
|
|
|
|
elseif item["type"] == "key" then
|
|
|
|
|
itemAddress = key_item_start_address + item["itemID"]
|
|
|
|
|
itemCount = memory.read_u8(itemAddress)
|
|
|
|
|
itemCount = itemCount + item["count"]
|
|
|
|
|
memory.write_u8(itemAddress, itemCount)
|
|
|
|
|
-- HPMemory will increase the internal counter but not actually increase the HP. If the item is one of those, do that
|
|
|
|
|
if item["itemID"] == 96 then
|
|
|
|
|
changeMaxHealth(20)
|
|
|
|
|
end
|
|
|
|
|
-- Same for the RegUps, but there's three of those
|
|
|
|
|
if item["itemID"] == 98 then
|
|
|
|
|
changeRegMemory(1)
|
|
|
|
|
end
|
|
|
|
|
if item["itemID"] == 99 then
|
|
|
|
|
changeRegMemory(2)
|
|
|
|
|
end
|
|
|
|
|
if item["itemID"] == 100 then
|
|
|
|
|
changeRegMemory(3)
|
|
|
|
|
end
|
|
|
|
|
gui.addmessage("Received Key Item "..item["itemName"].." from player "..item["sender"])
|
|
|
|
|
-- print("Received Key Item "..item["itemName"].." from player "..item["sender"])
|
|
|
|
|
elseif item["type"] == "subchip" then
|
|
|
|
|
addSubChip(item["itemID"], item["count"])
|
|
|
|
|
gui.addmessage("Received SubChip "..item["itemName"].." from player "..item["sender"])
|
|
|
|
|
-- print("Received SubChip "..item["itemName"].." from player "..item["sender"])
|
|
|
|
|
elseif item["type"] == "zenny" then
|
|
|
|
|
changeZenny(item["count"])
|
|
|
|
|
gui.addmessage("Received "..item["count"].."z from "..item["sender"])
|
|
|
|
|
-- print("Received "..item["count"].."z from "..item["sender"])
|
|
|
|
|
elseif item["type"] == "program" then
|
|
|
|
|
addProgram(item["itemID"], item["subItemID"], item["count"])
|
|
|
|
|
gui.addmessage("Received Program "..item["itemName"].." from player "..item["sender"])
|
|
|
|
|
-- print("Received Program "..item["itemName"].." from player "..item["sender"])
|
|
|
|
|
elseif item["type"] == "bugfrag" then
|
|
|
|
|
changeFrags(item["count"])
|
|
|
|
|
gui.addmessage("Received "..item["count"].." BugFrag(s) from "..item["sender"])
|
|
|
|
|
-- print("Received "..item["count"].." BugFrag(s) from "..item["sender"])
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Set the flags for opening the shortcuts as soon as the Cybermetro passes are received to save having to check email
|
|
|
|
|
local OpenShortcuts = function()
|
|
|
|
|
if (memory.read_u8(key_item_start_address + 92) > 0) then
|
|
|
|
|
memory.write_u8(0x2000032, bit.bor(memory.read_u8(0x2000032),0x10))
|
|
|
|
|
end
|
|
|
|
|
-- if CSciPass
|
|
|
|
|
if (memory.read_u8(key_item_start_address + 93) > 0) then
|
|
|
|
|
memory.write_u8(0x2000032, bit.bor(memory.read_u8(0x2000032),0x08))
|
|
|
|
|
end
|
|
|
|
|
if (memory.read_u8(key_item_start_address + 94) > 0) then
|
|
|
|
|
memory.write_u8(0x2000032, bit.bor(memory.read_u8(0x2000032),0x20))
|
|
|
|
|
end
|
|
|
|
|
if (memory.read_u8(key_item_start_address + 95) > 0) then
|
|
|
|
|
memory.write_u8(0x2000032, bit.bor(memory.read_u8(0x2000032),0x40))
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local RestoreItemRam = function()
|
|
|
|
|
if backup_bytes ~= nil then
|
|
|
|
|
memory.write_bytes_as_array(0x203fe10, backup_bytes)
|
|
|
|
|
end
|
|
|
|
|
backup_bytes = nil
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local process_block = function(block)
|
|
|
|
|
-- Sometimes the block is nothing, if this is the case then quietly stop processing
|
|
|
|
|
if block == nil then
|
|
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
debugEnabled = block['debug']
|
|
|
|
|
-- Queue item for receiving, if one exists
|
|
|
|
|
if (itemsReceived ~= block['items']) then
|
|
|
|
|
itemsReceived = block['items']
|
|
|
|
|
end
|
|
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local itemStateMachineProcess = function()
|
|
|
|
|
if itemState == ITEMSTATE_NONINITIALIZED then
|
|
|
|
|
itemQueueCounter = 120
|
|
|
|
|
-- Only exit this state the first time a dialog window pops up. This way we know for sure that we're ready to receive
|
|
|
|
|
if not IsInMenu() and (IsInDialog() or IsInTransition()) then
|
|
|
|
|
itemState = ITEMSTATE_NONITEM
|
|
|
|
|
end
|
|
|
|
|
elseif itemState == ITEMSTATE_NONITEM then
|
|
|
|
|
itemQueueCounter = 120
|
|
|
|
|
-- Always attempt to restore the previously stored memory in this state
|
|
|
|
|
-- Exit this state whenever the game is in an itemable status
|
|
|
|
|
if IsItemable() then
|
|
|
|
|
itemState = ITEMSTATE_IDLE
|
|
|
|
|
end
|
|
|
|
|
elseif itemState == ITEMSTATE_IDLE then
|
|
|
|
|
-- Remain Idle until an item is sent or we enter a non itemable status
|
|
|
|
|
if not IsItemable() then
|
|
|
|
|
itemState = ITEMSTATE_NONITEM
|
|
|
|
|
end
|
|
|
|
|
if itemQueueCounter == 0 then
|
|
|
|
|
if #itemsReceived > loadItemIndexFromRAM() and not IsItemQueued() then
|
|
|
|
|
itemQueued = itemsReceived[loadItemIndexFromRAM()+1]
|
|
|
|
|
SendItem(itemQueued)
|
|
|
|
|
itemState = ITEMSTATE_SENT
|
|
|
|
|
end
|
|
|
|
|
else
|
|
|
|
|
itemQueueCounter = itemQueueCounter - 1
|
|
|
|
|
end
|
|
|
|
|
elseif itemState == ITEMSTATE_SENT then
|
|
|
|
|
-- Once the item is sent, wait for the dialog to close. Then clear the item bit and be ready for the next item.
|
|
|
|
|
if IsInTransition() or IsInMenu() or IsOnTitle() then
|
|
|
|
|
itemState = ITEMSTATE_NONITEM
|
|
|
|
|
itemQueued = nil
|
|
|
|
|
RestoreItemRam()
|
|
|
|
|
elseif not IsInDialog() then
|
|
|
|
|
itemState = ITEMSTATE_IDLE
|
|
|
|
|
saveItemIndexToRAM(itemQueued["itemIndex"])
|
|
|
|
|
itemQueued = nil
|
|
|
|
|
RestoreItemRam()
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
local receive = function()
|
|
|
|
|
l, e = mmbn3Socket:receive()
|
|
|
|
|
|
|
|
|
|
-- Handle incoming message
|
|
|
|
|
if e == 'closed' then
|
|
|
|
|
if curstate == STATE_OK then
|
|
|
|
|
print("Connection closed")
|
|
|
|
|
end
|
|
|
|
|
curstate = STATE_UNINITIALIZED
|
|
|
|
|
return
|
|
|
|
|
elseif e == 'timeout' then
|
|
|
|
|
print("timeout")
|
|
|
|
|
return
|
|
|
|
|
elseif e ~= nil then
|
|
|
|
|
print(e)
|
|
|
|
|
curstate = STATE_UNINITIALIZED
|
|
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
process_block(json.decode(l))
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
local send = function()
|
|
|
|
|
-- Determine message to send back
|
|
|
|
|
local retTable = {}
|
|
|
|
|
retTable["playerName"] = loadPlayerNameFromROM()
|
|
|
|
|
retTable["scriptVersion"] = script_version
|
|
|
|
|
retTable["locations"] = check_all_locations()
|
|
|
|
|
retTable["gameComplete"] = is_game_complete()
|
|
|
|
|
|
|
|
|
|
-- Send the message
|
|
|
|
|
msg = json.encode(retTable).."\n"
|
|
|
|
|
local ret, error = mmbn3Socket:send(msg)
|
|
|
|
|
|
|
|
|
|
if ret == nil then
|
|
|
|
|
print(error)
|
|
|
|
|
elseif curstate == STATE_INITIAL_CONNECTION_MADE then
|
|
|
|
|
curstate = STATE_TENTATIVELY_CONNECTED
|
|
|
|
|
elseif curstate == STATE_TENTATIVELY_CONNECTED then
|
|
|
|
|
print("Connected!")
|
|
|
|
|
curstate = STATE_OK
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
function main()
|
|
|
|
|
if (bizhawk_major > 2 or (bizhawk_major == 2 and bizhawk_minor >= 7)==false) then
|
|
|
|
|
print("Must use a version of bizhawk 2.7.0 or higher")
|
|
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
server, error = socket.bind('localhost', 28922)
|
|
|
|
|
|
|
|
|
|
while true do
|
|
|
|
|
frame = frame + 1
|
|
|
|
|
|
|
|
|
|
if not (curstate == prevstate) then
|
|
|
|
|
prevstate = curstate
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
itemStateMachineProcess()
|
|
|
|
|
|
|
|
|
|
if (curstate == STATE_OK) or (curstate == STATE_INITIAL_CONNECTION_MADE) or (curstate == STATE_TENTATIVELY_CONNECTED) then
|
|
|
|
|
-- If we're connected and everything's fine, receive and send data from the network
|
|
|
|
|
if (frame % 60 == 0) then
|
|
|
|
|
receive()
|
|
|
|
|
send()
|
|
|
|
|
-- Perform utility functions which read and write data but aren't directly related to checks
|
|
|
|
|
OpenShortcuts()
|
|
|
|
|
end
|
|
|
|
|
elseif (curstate == STATE_UNINITIALIZED) then
|
|
|
|
|
-- If we're uninitialized, attempt to make the connection.
|
|
|
|
|
if (frame % 120 == 0) then
|
|
|
|
|
server:settimeout(2)
|
|
|
|
|
local client, timeout = server:accept()
|
|
|
|
|
if timeout == nil then
|
|
|
|
|
print('Initial Connection Made')
|
|
|
|
|
curstate = STATE_INITIAL_CONNECTION_MADE
|
|
|
|
|
mmbn3Socket = client
|
|
|
|
|
mmbn3Socket:settimeout(0)
|
|
|
|
|
else
|
|
|
|
|
print('Connection failed, ensure MMBN3Client is running and rerun connector_mmbn3.lua')
|
|
|
|
|
return
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
-- Handle the debug data display
|
|
|
|
|
gui.cleartext()
|
|
|
|
|
if debugEnabled then
|
|
|
|
|
-- gui.text(0,0,"Item Queued: "..tostring(IsItemQueued()))
|
|
|
|
|
-- gui.text(0,16,"In Battle: "..tostring(IsInBattle()))
|
|
|
|
|
-- gui.text(0,32,"In Dialog: "..tostring(IsInDialog()))
|
|
|
|
|
-- gui.text(0,48,"In Menu: "..tostring(IsInMenu()))
|
|
|
|
|
gui.text(0,48,"Item Wait Time: "..tostring(itemQueueCounter))
|
|
|
|
|
gui.text(0,64,itemState)
|
|
|
|
|
if itemQueued == nil then
|
|
|
|
|
gui.text(0,80,"No item queued")
|
|
|
|
|
else
|
|
|
|
|
gui.text(0,80,itemQueued["type"].." "..itemQueued["itemID"])
|
|
|
|
|
end
|
|
|
|
|
gui.text(0,96,"Item Index: "..loadItemIndexFromRAM())
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
emu.frameadvance()
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
main()
|