aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xsrb.py26
-rw-r--r--srb_lib.py55
-rwxr-xr-xsrb_tk.py40
3 files changed, 71 insertions, 50 deletions
diff --git a/srb.py b/srb.py
index c7b7f88..725ab88 100755
--- a/srb.py
+++ b/srb.py
@@ -13,7 +13,7 @@
# Reference:
# bgconf.py
# Improve:
-# still some confusion around the --unlock-everything --lock-everything and the levelset available levels.
+# still some confusion around the --unlock-everything --lock-everything and the levelset completed levels.
# Documentation: README.md
# Dependencies:
@@ -54,7 +54,7 @@ parser.add_argument("--set-level-status",action="append",help="Set completion st
parser.add_argument("--set-level-balloons",action="append",help="Set balloon status for this level for profile. Example value to pass: \"0,all\" or \"0,none\"")
parser.add_argument("--set-level-letters",action="append",help="Set collected letters for level for profile. Examples: 0,all 15,none")
parser.add_argument("--get-levelset",help="Print status for this levelset for profile.")
-parser.add_argument("--set-levelset-available-levels",action="append",help="Set number of available levels in this levelset for profile.")
+parser.add_argument("--set-levelset-completed-levels",action="append",help="Set number of completed levels in this levelset for profile.")
parser.add_argument("--get-name",action="store_true",help="Print name for profile.")
parser.add_argument("--set-name",help="Set name for profile.")
parser.add_argument("--get-profile-in-use",action="store_true",help="Print if profile is in use.")
@@ -89,7 +89,7 @@ profile_id = args.profile
#print(f"profile_id={profile_id}")
# New actions that need --profile must be added here.
-if not profile_id and (args.get_money or args.set_money or args.get_weapon or args.set_weapon or args.get_level or args.get_name or args.set_name or args.get_profile_in_use or args.get_purchased_weapons or args.get_tutorial_completed or args.add_purchased_weapons or args.remove_purchased_weapons or args.get_health or args.get_stunt or args.get_gun or args.set_health or args.set_stunt or args.set_gun or args.get_levelset or args.get_purchased_planes or args.add_purchased_planes or args.remove_purchased_planes or args.set_level_status or args.set_level_balloons or args.set_levelset_available_levels or args.set_level_letters or args.unlock_everything or args.lock_everything or args.buy_everything or args.unbuy_everything or args.set_level or args.set_profile_in_use):
+if not profile_id and (args.get_money or args.set_money or args.get_weapon or args.set_weapon or args.get_level or args.get_name or args.set_name or args.get_profile_in_use or args.get_purchased_weapons or args.get_tutorial_completed or args.add_purchased_weapons or args.remove_purchased_weapons or args.get_health or args.get_stunt or args.get_gun or args.set_health or args.set_stunt or args.set_gun or args.get_levelset or args.get_purchased_planes or args.add_purchased_planes or args.remove_purchased_planes or args.set_level_status or args.set_level_balloons or args.set_levelset_completed_levels or args.set_level_letters or args.unlock_everything or args.lock_everything or args.buy_everything or args.unbuy_everything or args.set_level or args.set_profile_in_use):
ferror("Warning: Cannot perform most actions without --profile. Not all tasks may run.")
else:
if args.get_money:
@@ -233,17 +233,17 @@ else:
ferror(f"Failed to add purchased planes {args.remove_purchased_planes} because {message}")
else:
srb_lib.write_file(args.file,0,data)
- if args.set_levelset_available_levels:
- for l in args.set_levelset_available_levels:
+ if args.set_levelset_completed_levels:
+ for l in args.set_levelset_completed_levels:
levelset_num = -1
try:
levelset_num, count = l.split(",")
except:
- ferror(f"Warning! Skipping un-parseable levelset available spec {l}. Please use \"0,all\" format.")
+ ferror(f"Warning! Skipping un-parseable levelset completed spec {l}. Please use \"0,all\" format.")
if levelset_num != -1:
- data, message = srb_lib.set_levelset_available_levels(args.file,profile_id,levelset_num,count)
+ data, message = srb_lib.set_levelset_completed_levels(args.file,profile_id,levelset_num,count)
if (type(data) == int and data == -1) or message != "":
- ferror(f"Failed to set profile {profile_id} level {levelset_num} available levels to {count} because {message}")
+ ferror(f"Failed to set profile {profile_id} level {levelset_num} completed levels to {count} because {message}")
else:
srb_lib.write_file(args.file,0,data)
if args.set_level_letters:
@@ -256,13 +256,13 @@ else:
if level_num != -1:
data, message = srb_lib.set_level_letters(args.file,profile_id,level_num, count)
if (type(data) == int and data == -1) or message != "":
- ferror(f"Failed to set profile {profile_id} level {levelset_num} available levels to {count} because {message}")
+ ferror(f"Failed to set profile {profile_id} level {levelset_num} completed levels to {count} because {message}")
else:
#print(f"good?")
srb_lib.write_file(args.file,0,data)
if args.unlock_everything:
data = srb_lib._get_data_from_data_object(args.file)
- # counting backwards helps the levelset available levels count.
+ # counting backwards helps the levelset completed levels count.
for i in range(len(srb_lib.LEVELS)-1,0,-1):
data, new_status, message = srb_lib.set_level_status(data,profile_id,i,"general")
if message != "":
@@ -277,7 +277,7 @@ else:
ferror(f"Failed on loop {i}, set_level_letters, {message}")
break
for i in range(len(srb_lib.LEVELSETS)-1,0,-1):
- data, message = srb_lib.set_levelset_available_levels(data,profile_id,i,"all")
+ data, message = srb_lib.set_levelset_completed_levels(data,profile_id,i,"all")
if message != "":
ferror(f"Failed on levelset loop {i}, {message}")
break
@@ -287,7 +287,7 @@ else:
srb_lib.write_file(args.file,0,data)
if args.lock_everything:
data = srb_lib._get_data_from_data_object(args.file)
- # counting backwards helps the levelset available levels count.
+ # counting backwards helps the levelset completed levels count.
for i in range(len(srb_lib.LEVELS)-1,0,-1):
data, new_status, message = srb_lib.set_level_status(data,profile_id,i,"none")
if message != "":
@@ -302,7 +302,7 @@ else:
ferror(f"Failed on loop {i}, set_level_letters, {message}")
break
for i in range(len(srb_lib.LEVELSETS)-1,0,-1):
- data, message = srb_lib.set_levelset_available_levels(data,profile_id,i,"none")
+ data, message = srb_lib.set_levelset_completed_levels(data,profile_id,i,"none")
if message != "":
ferror(f"Failed on levelset loop {i}, {message}")
break
diff --git a/srb_lib.py b/srb_lib.py
index f7f79a5..be3683a 100644
--- a/srb_lib.py
+++ b/srb_lib.py
@@ -28,7 +28,7 @@
# Dependencies:
import sys, struct
-srb_lib_version = "20240404a"
+srb_lib_version = "20240405a"
# Table of byte positions of values in the savegame file, minus the first four bytes which are the checksum. Due to zero-indexing of python lists, but for ease of usage, we will always put a zero as the value of index 0. That is, profile 1 will use index 1 of the list.
# "Z<dddddddd" is the start of a profile.
@@ -104,17 +104,18 @@ PLANES = [
{"p":0x3B20,"name":"all"},
]
-# c = balloon color
# l = level count
-# le = letters, calculated
+# a = level 0 is Available if previous levelset has this number of completed missions
+# c = balloon color
# b = balloon count, calculated
+# le = letters, calculated
LEVELSETS = [
- {"id":0,"l":6,"c":"red" ,"b":0,"le":"","name":"Aerodrome Island"},
- {"id":1,"l":3,"c":"yellow","b":0,"le":"","name":"Woods of Montsec"},
- {"id":2,"l":3,"c":"green" ,"b":0,"le":"","name":"Front Lines of Verdun"},
- {"id":3,"l":5,"c":"blue" ,"b":0,"le":"","name":"Mines of the Matterhorn"},
- {"id":4,"l":3,"c":"orange","b":0,"le":"","name":"Verdon Gorge"},
- {"id":5,"l":2,"c":"none" ,"b":0,"le":"","name":"Flying Fortress"},
+ {"id":0,"l":6,"a":0,"c":"red" ,"b":0,"le":"","name":"Aerodrome Island"},
+ {"id":1,"l":3,"a":3,"c":"yellow","b":0,"le":"","name":"Woods of Montsec"},
+ {"id":2,"l":3,"a":2,"c":"green" ,"b":0,"le":"","name":"Front Lines of Verdun"},
+ {"id":3,"l":5,"a":2,"c":"blue" ,"b":0,"le":"","name":"Mines of the Matterhorn"},
+ {"id":4,"l":3,"a":5,"c":"orange","b":0,"le":"","name":"Verdon Gorge"},
+ {"id":5,"l":2,"a":3,"c":"none" ,"b":0,"le":"","name":"Flying Fortress"}
]
# pos_r = relative position for level status
@@ -411,7 +412,7 @@ def get_collected_breakables(data_object,profile_id, level, silent=False):
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,fix_levelset_available_levels=True):
+def set_level_status(data_object,profile_id,level,status,fix_levelset_completed_levels=True):
""" Set completion rank for a level, e.g., general """
data = _get_data_from_data_object(data_object)
level_obj, message = get_level_info(level)
@@ -427,19 +428,19 @@ def set_level_status(data_object,profile_id,level,status,fix_levelset_available_
#print(f"debug: will try to set bits {bits:02x}")
data = srb_pack('<1I',data,PROFILE_START_POSITION[profile_id]+POS_LEVEL_START+(INT_SIZE*level_obj["pos_r"]),bits)
current_status, _, _ = get_level_status(data, profile_id, level, silent=True)
- levelset_available_levels = get_levelset_available_levels(data,profile_id,level_obj["setid"])
- #print(f"debug: levelset {level_obj['setid']} currently has {levelset_available_levels} available levels.")
- # if setting to any completed status, if the levelset available levels is less than this level, then make it this.
- if fix_levelset_available_levels:
- if levelset_available_levels < (level_obj["set_pos"] + 1) and status not in ["none"]:
- data, message = set_levelset_available_levels(data,profile_id,level_obj["setid"],level_obj["set_pos"] + 1)
+ levelset_completed_levels = get_levelset_completed_levels(data,profile_id,level_obj["setid"])
+ #print(f"debug: levelset {level_obj['setid']} currently has {levelset_completed_levels} completed levels.")
+ # if setting to any completed status, if the levelset completed levels is less than this level, then make it this.
+ if fix_levelset_completed_levels:
+ if levelset_completed_levels < (level_obj["set_pos"] + 1) and status not in ["none"]:
+ data, message = set_levelset_completed_levels(data,profile_id,level_obj["setid"],level_obj["set_pos"] + 1)
if message != "":
- return -1, -1, f"Unable to set levelset available levels to minimum of this level set_pos {level_obj['set_pos']}"
- # decrement the available levels in the levelset if clearing out this level and the available levels is exactly this one.
- if levelset_available_levels == (level_obj["set_pos"] + 1) and status in ["none"]:
- data, message = set_levelset_available_levels(data,profile_id,level_obj["setid"],level_obj["set_pos"])
+ return -1, -1, f"Unable to set levelset completed levels to minimum of this level set_pos {level_obj['set_pos']}"
+ # decrement the completed levels in the levelset if clearing out this level and the completed levels is exactly this one.
+ if levelset_completed_levels == (level_obj["set_pos"] + 1) and status in ["none"]:
+ data, message = set_levelset_completed_levels(data,profile_id,level_obj["setid"],level_obj["set_pos"])
if message != "":
- return -1, -1, f"Unable to decrement levelset available levels."
+ return -1, -1, f"Unable to decrement levelset completed levels."
return data, current_status, ""
def set_level_balloons(data_object,profile_id,level,count, silent = False):
@@ -575,19 +576,19 @@ def set_level_letters(data_object,profile_id,level,letters, silent = False):
data = srb_pack('<1I',data,pos_levelset_completed_letters_mask,new_letter_mask_final)
return data, ""
-def get_levelset_available_levels(data_object,profile_id,levelset):
+def get_levelset_completed_levels(data_object,profile_id,levelset):
data = _get_data_from_data_object(data_object)
levelset_obj, message = get_levelset_info(levelset)
if message != "":
- return -1, f"For set_levelset_available_levels unable to get levelset for {levelset}."
+ return -1, f"For set_levelset_completed_levels unable to get levelset for {levelset}."
pos_levelset_completed_mission_mask = PROFILE_START_POSITION[profile_id]+POS_LEVELSET_COMPLETED_MISSIONS_MASK+(INT_SIZE*levelset_obj["id"])
return data[pos_levelset_completed_mission_mask]
-def set_levelset_available_levels(data_object,profile_id,levelset,completed_count):
+def set_levelset_completed_levels(data_object,profile_id,levelset,completed_count):
data = _get_data_from_data_object(data_object)
levelset_obj, message = get_levelset_info(levelset)
if message != "":
- return -1, f"For set_levelset_available_levels unable to get levelset for {levelset}."
+ return -1, f"For set_levelset_completed_levels unable to get levelset for {levelset}."
if completed_count == "all":
completed_count = 8 # no levelset has more than 6 levels so this is a safe maximum, and it will get checked farther below.
if completed_count == "none":
@@ -596,13 +597,13 @@ def set_levelset_available_levels(data_object,profile_id,levelset,completed_coun
try:
completed_count = int(completed_count)
except:
- return -1, f"cannot set levelset available levels to {completed_count}"
+ return -1, f"cannot set levelset completed levels to {completed_count}"
if completed_count < 0:
completed_count = 0
completed_count = min(levelset_obj["l"], completed_count)
pos_levelset_completed_mission_mask = PROFILE_START_POSITION[profile_id]+POS_LEVELSET_COMPLETED_MISSIONS_MASK+(INT_SIZE*levelset_obj["id"])
completed_bitmask = pow(2,completed_count)-1
- #print(f"debug: need to set levelset {levelset} available count to {completed_count}, which stored as a bitmask should be {completed_bitmask:07b}")
+ #print(f"debug: need to set levelset {levelset} completed count to {completed_count}, which stored as a bitmask should be {completed_bitmask:07b}")
data = srb_pack('<1I',data,pos_levelset_completed_mission_mask,completed_bitmask)
return data, ""
diff --git a/srb_tk.py b/srb_tk.py
index f78569a..dda1029 100755
--- a/srb_tk.py
+++ b/srb_tk.py
@@ -23,8 +23,6 @@
# https://stackoverflow.com/questions/67334913/can-i-possibly-put-an-image-label-into-an-option-box-in-tkinter
# Improve:
# Add reset-level-breakables
-# disable/gray out levels labels not reachable because of levelset_available_levels
-# setting that sets the background colors of the regions
# Dependencies:
# dep-devuan: python3-tkinter, python3-pil.imagetk
# rec-devuan: python3-cairosvg
@@ -302,10 +300,8 @@ class App(tk.Frame):
self.lbl_levelsets = []
self.spn_levelsets = []
self.ent_levelsets_balloons = []
- self.lbl_levelsets_name = tk.Label(self.frm_levels,text="Billboard/Available")
+ self.lbl_levelsets_name = tk.Label(self.frm_levels,text="Billboard/Completed")
self.lbl_levelsets_name.grid(row=0,column=0,sticky="EW",columnspan=4)
- #self.lbl_levelsets_available_levels = tk.Label(self.frm_levels,text="Available")
- #self.lbl_levelsets_available_levels.grid(row=0,column=1,columnspan=2)
self.lbl_levels_name = tk.Label(self.frm_levels,text="Level")
self.lbl_levels_name.grid(row=0,column=5,sticky="EW")
self.lbl_levels_status = tk.Label(self.frm_levels,text="Rank")
@@ -505,15 +501,15 @@ class App(tk.Frame):
bdata, message = srb_lib.set_purchased_planes(bdata, profile_id, "add", pur_planes_list, silent=self.silent.get())
if bdata == -1 or message != "":
raise Exception(f"Failed when setting purchased planes, message {message}.")
- # levelset available levels
+ # levelset completed levels
for i in range(0,len(srb_lib.LEVELSETS)):
- bdata, message = srb_lib.set_levelset_available_levels(bdata, profile_id, i, self.levelset_status_ints[i].get())
+ bdata, message = srb_lib.set_levelset_completed_levels(bdata, profile_id, i, self.levelset_status_ints[i].get())
if bdata == -1 or message != "":
- raise Exception(f"Failed to set levelset available levels for levelset {i}, message {message}.")
+ raise Exception(f"Failed to set levelset completed levels for levelset {i}, message {message}.")
for i in range(0,len(srb_lib.LEVELS)):
ts = self.level_statuses[i].get()
if ts != "unknown":
- bdata, current_status, message = srb_lib.set_level_status(bdata, profile_id, i, ts, fix_levelset_available_levels=False)
+ bdata, current_status, message = srb_lib.set_level_status(bdata, profile_id, i, ts, fix_levelset_completed_levels=False)
if bdata == -1 or current_status == -1 or message != "":
raise Exception(f"Failed to set level status for {i} to {ts}, because {message}")
except Exception as e:
@@ -688,7 +684,7 @@ class App(tk.Frame):
self.pur_plane_str_woodstock.set(letters)
elif 5 == x:
self.pur_plane_str_baron.set(letters)
- # levelset available levels
+ # levelset completed levels
mission_mask = bin(int(mission_mask[1:],2)).count('1')
#print(f"for levelset {i}, need to set max {i['l']}, current {mission_mask}")
self.levelset_status_ints[x].set(mission_mask)
@@ -696,6 +692,7 @@ class App(tk.Frame):
x += 1
# levels
for i in range(0,len(srb_lib.LEVELS)):
+ this_level = [j for j in srb_lib.LEVELS if j["id"] == i][0]
tl_status, tl_balloons, tl_letters = srb_lib.get_level_status(self.bdata,profile_id,i,silent=True)
# level status
tl_status = int(tl_status,16)
@@ -711,6 +708,29 @@ class App(tk.Frame):
# level balloons
self.level_balloons[i].set(tl_balloons)
self.level_letters[i].set(tl_letters)
+ # level available because of completed levels
+ # we are not in the loop of levelsets, so we need to re-get mission_mask.
+ mission_mask, _, _ = srb_lib.get_levelset_status(self.bdata,profile_id,this_level["setid"],silent=True)
+ mission_mask = bin(int(mission_mask[1:],2)).count('1')
+ prev_mission_mask = 0
+ this_levelset = [l for l in srb_lib.LEVELSETS if l["id"] == this_level["setid"]][0]
+ if this_level["setid"] > 0:
+ prev_mission_mask, _, _ = srb_lib.get_levelset_status(self.bdata,profile_id,this_level["setid"]-1,silent=True)
+ prev_mission_mask = bin(int(prev_mission_mask[1:],2)).count('1')
+ # work up from here
+ state = "disabled"
+ if this_level["set_pos"] == 0:
+ if this_level["setid"] == 0:
+ if srb_lib.get_tutorial_completed(self.bdata,profile_id):
+ state = "normal"
+ elif prev_mission_mask >= this_levelset["a"]:
+ state = "normal"
+ elif this_level["set_pos"] <= mission_mask:
+ state = "normal"
+ self.lbl_levels[this_level['id']].config(state=state)
+ # if level set_pos 0, then update the label for the whole levelset
+ if this_level["set_pos"] == 0:
+ self.lbl_levelsets[this_level["setid"]].config(state=state)
# end if-profile_id is 1,2 or 3.
# mark-traces-enable must happen at the end!
self.mark_traces(True)
bgstack15