diff options
Diffstat (limited to 'srb_lib.py')
-rw-r--r-- | srb_lib.py | 91 |
1 files changed, 69 insertions, 22 deletions
@@ -55,6 +55,18 @@ POS_LEVEL_LETTERS_MULTIPLIER_LEVELSET = 7 POS_LEVEL_BALLOONS = 0x4B4 POS_LEVEL_BALLOONS_MULTIPLIER_LEVEL = 0x50 +# p = position in bitmask +# w = weapon id +BREAKABLES = [ + {"p":0x00,"w":0x0,"name":"none"}, + {"p":0x01,"w":0xD,"name":"Barrel"}, + {"p":0x02,"w":0xA,"name":"Haystack"}, + {"p":0x04,"w":0x9,"name":"Geyser"}, + {"p":0x08,"w":0xE,"name":"Pumpkin"}, + {"p":0x10,"w":0xB,"name":"Battery"}, + {"p":0x1F,"w":0x0,"name":"all"} +] + NAME_CHARS = " ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!?" # in order # for equipped-weapon @@ -106,29 +118,30 @@ LEVELSETS = [ # pos_r = relative position for level status # set_pos = level number within the levelset. Necessary for certain position calculations including letters. +# r: has breakables (haystack, barrels, etc.) LEVELS = [ - {"id":0, "pos_r":0, "set_pos":0,"setid":0,"b":10,"l":"m" ,"name":"Defend Island"}, - {"id":1, "pos_r":1, "set_pos":1,"setid":0,"b":10,"l":"a" ,"name":"Recover Plans"}, - {"id":2, "pos_r":2, "set_pos":2,"setid":0,"b":10,"l":"r" ,"name":"Protect the Trucks"}, - {"id":3, "pos_r":3, "set_pos":3,"setid":0,"b":10,"l":"c" ,"name":"Attack of the U-Boats"}, - {"id":4, "pos_r":4, "set_pos":4,"setid":0,"b":10,"l":"i" ,"name":"Cripple Outpost Island"}, - {"id":5, "pos_r":5, "set_pos":5,"setid":0,"b":10,"l":"e" ,"name":"Sink the Battleship"}, - {"id":6, "pos_r":7, "set_pos":0,"setid":1,"b":10,"l":"sa" ,"name":"Rerun's Challenge"}, - {"id":7, "pos_r":8, "set_pos":1,"setid":1,"b":10,"l":"l" ,"name":"Eliminate Tree Village"}, - {"id":8, "pos_r":9, "set_pos":2,"setid":1,"b":10,"l":"ly" ,"name":"Tree Chopper"}, - {"id":9, "pos_r":14,"set_pos":0,"setid":2,"b":10,"l":"re" ,"name":"Trench Warfare"}, - {"id":10,"pos_r":15,"set_pos":1,"setid":2,"b":10,"l":"r" ,"name":"Recover Allied Base"}, - {"id":11,"pos_r":16,"set_pos":2,"setid":2,"b":10,"l":"un" ,"name":"Giant Tank"}, - {"id":12,"pos_r":21,"set_pos":0,"setid":3,"b":10,"l":"pi" ,"name":"Surprise Attack"}, - {"id":13,"pos_r":22,"set_pos":1,"setid":3,"b":10,"l":"g" ,"name":"Derail the Train"}, - {"id":14,"pos_r":23,"set_pos":2,"setid":3,"b":10,"l":"p" ,"name":"Enter the Mines"}, - {"id":15,"pos_r":24,"set_pos":3,"setid":3,"b":10,"l":"e" ,"name":"Explore the Mines"}, - {"id":16,"pos_r":25,"set_pos":4,"setid":3,"b":10,"l":"n" ,"name":"Destroy Driller Boss"}, - {"id":17,"pos_r":28,"set_pos":0,"setid":4,"b":10,"l":"woo","name":"Navigate the Canyon"}, - {"id":18,"pos_r":29,"set_pos":1,"setid":4,"b":10,"l":"dst","name":"Destroy Circus City"}, - {"id":19,"pos_r":30,"set_pos":2,"setid":4,"b":10,"l":"ock","name":"Circus Aircraft Carrier"}, - {"id":20,"pos_r":35,"set_pos":0,"setid":5,"b": 0,"l":"bar","name":"Rescue Allies"}, - {"id":21,"pos_r":36,"set_pos":1,"setid":5,"b": 0,"l":"on" ,"name":"Battle the Red Baron"} + {"id":0, "pos_r":0, "set_pos":0,"setid":0,"r":True ,"b":10,"l":"m" ,"name":"Defend Island"}, + {"id":1, "pos_r":1, "set_pos":1,"setid":0,"r":True ,"b":10,"l":"a" ,"name":"Recover Plans"}, + {"id":2, "pos_r":2, "set_pos":2,"setid":0,"r":True ,"b":10,"l":"r" ,"name":"Protect the Trucks"}, + {"id":3, "pos_r":3, "set_pos":3,"setid":0,"r":True ,"b":10,"l":"c" ,"name":"Attack of the U-Boats"}, + {"id":4, "pos_r":4, "set_pos":4,"setid":0,"r":True ,"b":10,"l":"i" ,"name":"Cripple Outpost Island"}, + {"id":5, "pos_r":5, "set_pos":5,"setid":0,"r":False,"b":10,"l":"e" ,"name":"Sink the Battleship"}, + {"id":6, "pos_r":7, "set_pos":0,"setid":1,"r":True ,"b":10,"l":"sa" ,"name":"Rerun's Challenge"}, + {"id":7, "pos_r":8, "set_pos":1,"setid":1,"r":False,"b":10,"l":"l" ,"name":"Eliminate Tree Village"}, + {"id":8, "pos_r":9, "set_pos":2,"setid":1,"r":False,"b":10,"l":"ly" ,"name":"Tree Chopper"}, + {"id":9, "pos_r":14,"set_pos":0,"setid":2,"r":True ,"b":10,"l":"re" ,"name":"Trench Warfare"}, + {"id":10,"pos_r":15,"set_pos":1,"setid":2,"r":False,"b":10,"l":"r" ,"name":"Recover Allied Base"}, + {"id":11,"pos_r":16,"set_pos":2,"setid":2,"r":False,"b":10,"l":"un" ,"name":"Giant Tank"}, + {"id":12,"pos_r":21,"set_pos":0,"setid":3,"r":True ,"b":10,"l":"pi" ,"name":"Surprise Attack"}, + {"id":13,"pos_r":22,"set_pos":1,"setid":3,"r":False,"b":10,"l":"g" ,"name":"Derail the Train"}, + {"id":14,"pos_r":23,"set_pos":2,"setid":3,"r":True ,"b":10,"l":"p" ,"name":"Enter the Mines"}, + {"id":15,"pos_r":24,"set_pos":3,"setid":3,"r":False,"b":10,"l":"e" ,"name":"Explore the Mines"}, + {"id":16,"pos_r":25,"set_pos":4,"setid":3,"r":False,"b":10,"l":"n" ,"name":"Destroy Driller Boss"}, + {"id":17,"pos_r":28,"set_pos":0,"setid":4,"r":False,"b":10,"l":"woo","name":"Navigate the Canyon"}, + {"id":18,"pos_r":29,"set_pos":1,"setid":4,"r":True ,"b":10,"l":"dst","name":"Destroy Circus City"}, + {"id":19,"pos_r":30,"set_pos":2,"setid":4,"r":False,"b":10,"l":"ock","name":"Circus Aircraft Carrier"}, + {"id":20,"pos_r":35,"set_pos":0,"setid":5,"r":False,"b": 0,"l":"bar","name":"Rescue Allies"}, + {"id":21,"pos_r":36,"set_pos":1,"setid":5,"r":False,"b": 0,"l":"on" ,"name":"Battle the Red Baron"} ] # hex values @@ -356,9 +369,43 @@ def get_level_status(data_object,profile_id,level,silent=False): print(f"Debug: abs 0x{CHECKSUM_LENGTH+pos_level_balloons:04x} collected balloon count: {profile_level_balloons}/{level_obj['b']}") print(f"Debug: abs 0x{CHECKSUM_LENGTH+pos_level_which_balloons:04x} which balloons: b{profile_level_which_balloons:010b}") print(f"Debug: abs 0x{CHECKSUM_LENGTH+pos_level_letters:04x} letter count: {profile_level_letters}/{len(level_obj['l'])}") + # the breakables info is only useful if the level has breakable objects. Not all do. + if level_obj["r"]: + collected_breakables = get_collected_breakables(data,profile_id,level_obj["id"],silent=silent) + print(f"debug: got collected_breakables {collected_breakables}") # it comes back as an int, but does it look better as a hex? return hex(profile_level_status) +def get_collected_breakables(data_object,profile_id, level, silent=False): + """ Return a list of breakables that the profile has already collected for this level. """ + data = _get_data_from_data_object(data_object) + level_obj, message = get_level_info(level) + #print(f"debug (get_collected_breakables): got level_obj, message {level_obj}, {message}") + levelset_obj, message = get_levelset_info(level_obj["setid"]) + pos_level_balloons = PROFILE_START_POSITION[profile_id]+POS_LEVEL_BALLOONS+(level_obj["setid"]*POS_LEVEL_BALLOONS_MULTIPLIER_LEVELSET)+(POS_LEVEL_BALLOONS_MULTIPLIER_LEVEL*level_obj["set_pos"]) + pos_level_breakables = pos_level_balloons + (int(((INT_SIZE-levelset_obj["id"])*INT_SIZE+INT_SIZE)/2) + 2) * INT_SIZE + pos_level_which_breakables = pos_level_breakables + INT_SIZE + profile_level_breakables = struct.unpack_from('<1I',data,pos_level_breakables)[0] + profile_level_which_breakables = struct.unpack_from('<1I',data,pos_level_which_breakables)[0] + all_breakables_mask = [i for i in BREAKABLES if i["name"] == "all"][0]["p"] + none_breakables_mask = [i for i in BREAKABLES if i["name"] == "none"][0]["p"] + # short-circuit if all + if profile_level_which_breakables & all_breakables_mask == all_breakables_mask: + return "all", all_breakables_mask + # short-circuit if none + elif profile_level_breakables | none_breakables_mask == 0: + return "none", none_breakables_mask + breakables_list = [] + breakables_mask = 0x0 + for i in BREAKABLES: + if profile_level_which_breakables & i["p"] and (i["name"] not in ["all","none"]): + breakables_list.append(i["name"]) + breakables_mask += i["p"] + if not silent: + print(f"Debug: abs 0x{CHECKSUM_LENGTH+pos_level_breakables:04x} collected breakable count: {profile_level_breakables}/5") + print(f"Debug: abs 0x{CHECKSUM_LENGTH+pos_level_which_breakables:04x} which breakables: b{profile_level_which_breakables:05b}") + return ','.join(breakables_list), breakables_mask + def set_level_status(data_object,profile_id,level,status): data = _get_data_from_data_object(data_object) level_obj, message = get_level_info(level) |