diff options
-rw-r--r-- | srb_lib.py | 41 | ||||
-rwxr-xr-x | srb_tk.py | 72 |
2 files changed, 72 insertions, 41 deletions
@@ -412,14 +412,14 @@ 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): +def set_level_status(data_object,profile_id,level,status,fix_levelset_available_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) if message != "" or level_obj == -1: ferror(f"Unable to get level status for {level}.") current_status, _, _ = get_level_status(data, profile_id, level, silent=True) - print(f"Before changing, level {level} has status {current_status}") + #print(f"Before changing, level {level} has status {current_status}") bits = 0x0 try: bits = [i for i in LEVEL_STATUSES if i["name"]==status][0]["b"] @@ -429,17 +429,18 @@ def set_level_status(data_object,profile_id,level,status): 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.") + #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 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) - 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"]) - if message != "": - return -1, -1, f"Unable to decrement levelset available levels." + 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) + 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"]) + if message != "": + return -1, -1, f"Unable to decrement levelset available levels." return data, current_status, "" def set_level_balloons(data_object,profile_id,level,count): @@ -504,7 +505,7 @@ def get_levelset_status(data_object,profile_id,levelset,silent=False): # it comes back as an int, but does it look better as a hex? return profile_levelset_status, completed_letters -def set_level_letters(data_object,profile_id,level,letters): +def set_level_letters(data_object,profile_id,level,letters, silent = False): """ Set collected letters for given level to all or none. """ data = _get_data_from_data_object(data_object) level_obj, message = get_level_info(level) @@ -532,7 +533,8 @@ def set_level_letters(data_object,profile_id,level,letters): profile_levelset_completed_letters_mask = struct.unpack_from('<1I',data,pos_levelset_completed_letters_mask)[0] >> 1 # python trick to reverse a custom-formatted string and convert back to int while reading the string as base 2 profile_levelset_completed_letters_mask = int(f"{profile_levelset_completed_letters_mask:0{len(levelset_obj['le'])}b}"[::-1],2) - print(f"debug: levelset {levelset_obj['id']} before changes has letters mask {profile_levelset_completed_letters_mask:0{len(levelset_obj['le'])}b}") + if not silent: + print(f"debug: levelset {levelset_obj['id']} before changes has letters mask {profile_levelset_completed_letters_mask:0{len(levelset_obj['le'])}b}") levels_to_check = [i for i in LEVELS if i["setid"] == levelset_obj["id"]] #print(f"need to check levels {levels_to_check}") levelset_letters_count = 0 @@ -561,12 +563,13 @@ def set_level_letters(data_object,profile_id,level,letters): #print(f"debug: intermediate, new_letter_mask={new_letter_mask}") new_letter_mask = "".join(new_letter_mask) new_letter_mask_int = int(new_letter_mask,2) - #print(f"debug: new_letter_mask updated list is str {new_letter_mask}, int {new_letter_mask_int}") - #print(f"debug: total letters collected for levelset {levelset_obj['id']}: {levelset_letters_count}") - print(f"debug: new letters bitmask for levelset {levelset_obj['id']}: {new_letter_mask_int:0{len(new_letter_mask)}b}") + if not silent: + #print(f"debug: new_letter_mask updated list is str {new_letter_mask}, int {new_letter_mask_int}") + #print(f"debug: total letters collected for levelset {levelset_obj['id']}: {levelset_letters_count}") + print(f"debug: new letters bitmask for levelset {levelset_obj['id']}: {new_letter_mask_int:0{len(new_letter_mask)}b}") # bitmask needs to be reversed and left-1-bitshifted new_letter_mask_final = int(f"{new_letter_mask_int:0{len(levelset_obj['le'])}b}"[::-1],2) << 1 - print(f"debug: so that final bitmask should be {new_letter_mask_final:016b}") + #print(f"debug: so that final bitmask should be {new_letter_mask_final:016b}") # set levelset letter count data = srb_pack('<1I',data,pos_levelset_completed_letters_count,levelset_letters_count) # set levelset letter mask @@ -625,6 +628,8 @@ def get_collected_balloons_for_levelset(data_object,profile_id,levelset,silent=F def get_level_info(level): """ Returns dictionary of level from LEVELS, searching by id or name. """ + if level in LEVELS: + return level, "" try: # if it is an integer, make sure it shows up as one in the next check level = int(level) @@ -136,7 +136,6 @@ class App(tk.Frame): self.statustext = tk.StringVar() # app label, for vanity - # WORKHERE, vanity tag does not actually fill two columns. self.lbl_app = tk.Label(self.master,text="Savegame editor for Snoopy vs. the Red Baron") self.lbl_app.grid(row=0,column=0,columnspan=2) stk.Tooltip(self.lbl_app,text="Also checkout the cli with ./srb.py") @@ -148,7 +147,6 @@ class App(tk.Frame): self.chk_silent = tk.Checkbutton(self.master,text="silent",variable=self.silent) self.chk_silent.grid(row=2,column=1) self.frm_profiles = tk.Frame(self.master,background="orange",borderwidth=1) - # WORKHERE: firm up column/placement of things so there is less whitespace. self.frm_profiles.grid(row=3,column=0,columnspan=2) # investigate radio button key-bind, up-down, to increment/decrement choice? self.rad_prof1 = stk.Radiobutton(self.frm_profiles,value=1,text="Profile 1",variable=self.selected_profile,underline=8) @@ -279,21 +277,20 @@ class App(tk.Frame): x = 1 for i in range(0,len(srb_lib.LEVELSETS)): tl = srb_lib.get_levelset_info(i)[0] - # WORKHERE: will have to set up variables for all this stuff, and then populate this stuff only on the load_form_into_data_for_profile. self.lbl_levelsets.append(tk.Label(self.frm_levels,text=tl["name"])) self.lbl_levelsets[i].grid(row=x,column=0,sticky="EW",columnspan=2) self.spn_levelsets.append(stk.Spinbox(self.frm_levels,textvariable=self.levelset_status_ints[i],from_=0,to=tl["l"],width=2)) self.spn_levelsets[i].grid(row=x+1,column=0) tk.Label(self.frm_levels,text="/ " + str(tl["l"])).grid(row=x+1,column=1,sticky="EW") - print(f"For levelset {tl},") + #print(f"For levelset {tl},") these_levels = [k for k in srb_lib.LEVELS if k["setid"] == tl["id"]] sorted(these_levels,key=lambda i: i["id"]) y = 0 for j in these_levels: - print(f"Process level {j}") + #print(f"Process level {j}") self.lbl_levels.append(tk.Label(self.frm_levels,text=j["name"])) self.lbl_levels[-1].grid(row=x,column=3) - self.opt_levels_hex.append(tk.Entry(self.frm_levels,state="readonly",textvariable=self.level_statuses_hex[j["id"]],width=3)) + self.opt_levels_hex.append(tk.Entry(self.frm_levels,state="readonly",textvariable=self.level_statuses_hex[j["id"]],width=2)) self.opt_levels_hex[-1].grid(row=x,column=4) self.opt_levels.append(tk.OptionMenu(self.frm_levels,self.level_statuses[j["id"]],*statuses)) self.opt_levels[-1].config(indicatoron=False,padx=0,pady=0,takefocus=1,width=8) @@ -303,17 +300,17 @@ class App(tk.Frame): self.ent_levels_balloons.append(tk.Entry(self.frm_levels,state="readonly",textvariable=self.level_balloons[j["id"]],width=2)) self.ent_levels_balloons[-1].grid(row=x,column=6) tk.Label(self.frm_levels,text="/ " + str(j["b"])).grid(row=x,column=7) - self.btn_balloons_none.append(tk.Button(self.frm_levels,text="none",padx=0,pady=0)) + self.btn_balloons_none.append(tk.Button(self.frm_levels,text="none",padx=0,pady=0,command=lambda tl=j,action="none",ty="balloons": self.button_action_level(tl,action,ty))) self.btn_balloons_none[-1].grid(row=x,column=8) - self.btn_balloons_all.append(tk.Button(self.frm_levels,text="all",padx=0,pady=0)) + self.btn_balloons_all.append(tk.Button(self.frm_levels,text="all",padx=0,pady=0,command=lambda tl=j,action="all",ty="balloons": self.button_action_level(tl,action,ty))) self.btn_balloons_all[-1].grid(row=x,column=9) # every level has letters though self.ent_levels_letters.append(tk.Entry(self.frm_levels,state="readonly",textvariable=self.level_letters[j["id"]],width=2)) self.ent_levels_letters[-1].grid(row=x,column=10) tk.Label(self.frm_levels,text="/ " + str(len(str(j["l"])))).grid(row=x,column=11) - self.btn_letters_none.append(tk.Button(self.frm_levels,text="none",padx=0,pady=0)) + self.btn_letters_none.append(tk.Button(self.frm_levels,text="none",padx=0,pady=0,command=lambda tl=j,action="none",ty="letters": self.button_action_level(tl,action,ty))) self.btn_letters_none[-1].grid(row=x,column=12) - self.btn_letters_all.append(tk.Button(self.frm_levels,text="all",padx=0,pady=0)) + self.btn_letters_all.append(tk.Button(self.frm_levels,text="all",padx=0,pady=0,command=lambda tl=j,action="all",ty="letters": self.button_action_level(tl,action,ty))) self.btn_letters_all[-1].grid(row=x,column=13) x += 1 x += 1 @@ -325,6 +322,26 @@ class App(tk.Frame): # and now open the first file self.func_open(self.current_file.get()) + def button_action_level(self, level_obj, action, type_): + """ Handle button press from "none" and "all" for balloons and letters for a level """ + #print(f"debug (button_action_level): {level_obj}, {action}, {type_}") + if type_ not in ["balloons","letters"]: + raise Exception("Button for level action got invalid type {type_}") + action = action.lower() + if action not in ["none","all"]: + raise Exception("Button for level action got invalid action {action}.") + if level_obj not in srb_lib.LEVELS: + raise Exception("Button for level action got invalid level {level_obj}.") + profile_id = self.selected_profile.get() + bdata = self.bdata + func = srb_lib.set_level_balloons if type_ == "balloons" else srb_lib.set_level_letters + bdata, message = func(bdata,profile_id,level_obj,action, silent=self.silent.get()) + if bdata == -1 or message != "": + raise Exception("Failed to set {type_} to {action} for level {level_obj}, because {message}") + else: + self.bdata = bdata + self.load_form_into_data() + def load_form_into_data(self, arg1 = None, arg2 = None, arg3 = None): """ Update self.bdata with the values currently displayed/edited on the form. This also updates the checksum. @@ -415,6 +432,12 @@ class App(tk.Frame): bdata, message = srb_lib.set_levelset_available_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}.") + 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) + 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: bdata = -1 print(f"ERROR! Invalid {e}") @@ -475,9 +498,9 @@ class App(tk.Frame): """ self.func_open(self.current_file.get()) - def mark_traces(self,action): + def mark_traces(self,enable = False): """ Enable or disable traces for variables. This protects the form from always calling load-form-into-data while we are populating the form initially! """ - if action == "enable": + if enable: for j in [ self.health, self.stunt, @@ -486,10 +509,14 @@ class App(tk.Frame): #self.prof1name, # do not trace the profile names, because it prevents spaces ending on the variables #self.prof2name, #self.prof3name, - ] + [i for i in self.levelset_status_ints]: + ] + self.levelset_status_ints + self.level_statuses: + #print(f"adding w trace for {j}") j.trace("w",self.load_form_into_data) # different one - self.selected_profile.trace("w",self.load_data_into_form_for_current_profile) + for j in [ + self.selected_profile + ]: + j.trace("w",self.load_data_into_form_for_current_profile) else: # disable for j in [ self.health, @@ -497,18 +524,16 @@ class App(tk.Frame): self.stunt, self.gun, self.equipped_weapon, - #self.prof1name, - #self.prof2name, - #self.prof3name, - ] + [i for i in self.levelset_status_ints]: + ] + self.levelset_status_ints + self.level_statuses: for i in j.trace_info(): if i[0][0] == "write": + #print(f"removing w trace for {j}") j.trace_remove(i[0][0],i[1]) def load_data_into_form(self, arg1 = None, arg2 = None, arg3 = None): """ Update all fields in dialog with contents of bdata. """ #print(f"debug (load_data_into_form): {arg1}, {arg2}, {arg3}") - self.mark_traces("disable") + self.mark_traces(False) # set all variables that are tied to entry boxes self.checksum.set(hex(self.bchecksum)[2:]) self.prof1_used.set(srb_lib.get_profile_in_use(self.bdata,1)) @@ -525,7 +550,7 @@ class App(tk.Frame): self.prof3name.set(prof3name) self.load_data_into_form_for_current_profile() # not necessary because for_current_profile runs the enable. - #self.mark_traces("enable") + #self.mark_traces(True) def load_data_into_form_for_current_profile(self, arg1 = None, arg2 = None, arg3 = None): """ Determine the currently-selected radio button for profile and load those values into the form. """ @@ -537,7 +562,7 @@ class App(tk.Frame): #print(f"debug: arg2 {arg2}") #print(f"debug: arg3 {arg3}") if profile_id >= 1 and profile_id <= 3: - self.mark_traces("disable") + self.mark_traces(False) # money self.money.set(srb_lib.get_money(self.bdata,profile_id)) # equipped weapon @@ -596,7 +621,6 @@ class App(tk.Frame): #print(f"for levelset {i}, need to set max {i['l']}, current {mission_mask}") self.levelset_status_ints[x].set(mission_mask) x += 1 - self.mark_traces("enable") # levelset available levels for i in range(0,len(srb_lib.LEVELSETS)): this_mission_mask, collected_letters = srb_lib.get_levelset_status(self.bdata,profile_id,i,silent=True) @@ -607,7 +631,7 @@ class App(tk.Frame): tl_status = int(tl_status,16) # look up name of status if possible else put "unknown". Thankfully tk handles it gracefully and shows it, but does not let a user pick it if he selects the drop-down menu. tl_status_str = "" - print(f"got tl_status {tl_status}, type {type(tl_status)}") + #print(f"got tl_status {tl_status}, type {type(tl_status)}") try: tl_status_str = [i["name"] for i in srb_lib.LEVEL_STATUSES if i["b"] == tl_status][0] except: @@ -618,6 +642,8 @@ class App(tk.Frame): self.level_balloons[i].set(tl_balloons) self.level_letters[i].set(tl_letters) # end if-profile_id is 1,2 or 3. + # mark-traces-enable must happen at the end! + self.mark_traces(True) else: print(f"Error! Cannot load that profile_id into the form.") |