aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorB. Stack <bgstack15@gmail.com>2024-04-03 16:19:55 -0400
committerB. Stack <bgstack15@gmail.com>2024-04-03 16:19:55 -0400
commit08553334c13b996eae1e0dd508f6e0f57a4c26b3 (patch)
treea9f59528b85f3a4ef09bcc7979a82f92fe9e065f
parenttk: load all values from data (diff)
downloadsrb_lib-08553334c13b996eae1e0dd508f6e0f57a4c26b3.tar.gz
srb_lib-08553334c13b996eae1e0dd508f6e0f57a4c26b3.tar.bz2
srb_lib-08553334c13b996eae1e0dd508f6e0f57a4c26b3.zip
tk: minimum viable product
-rw-r--r--srb_lib.py41
-rwxr-xr-xsrb_tk.py72
2 files changed, 72 insertions, 41 deletions
diff --git a/srb_lib.py b/srb_lib.py
index c916c6b..d388385 100644
--- a/srb_lib.py
+++ b/srb_lib.py
@@ -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)
diff --git a/srb_tk.py b/srb_tk.py
index c9afee4..0f74e8d 100755
--- a/srb_tk.py
+++ b/srb_tk.py
@@ -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.")
bgstack15