aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorB. Stack <bgstack15@gmail.com>2024-03-28 11:55:24 -0400
committerB. Stack <bgstack15@gmail.com>2024-03-28 11:55:24 -0400
commit4be09a9c6ffcbe52f7fdb740b281f019d228fe0e (patch)
treec4ecc1287b67529eb0016076117543c5c672d895
parentfix %% in parser helper, and add image (diff)
downloadsrb_lib-4be09a9c6ffcbe52f7fdb740b281f019d228fe0e.tar.gz
srb_lib-4be09a9c6ffcbe52f7fdb740b281f019d228fe0e.tar.bz2
srb_lib-4be09a9c6ffcbe52f7fdb740b281f019d228fe0e.zip
get-level-info: add breakables data
-rw-r--r--srb_lib.py91
1 files changed, 69 insertions, 22 deletions
diff --git a/srb_lib.py b/srb_lib.py
index e671e42..d8226c5 100644
--- a/srb_lib.py
+++ b/srb_lib.py
@@ -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)
bgstack15