diff options
-rw-r--r-- | images/breakables/Barrel.png | bin | 0 -> 330 bytes | |||
-rw-r--r-- | images/breakables/Battery.png | bin | 0 -> 398 bytes | |||
-rw-r--r-- | images/breakables/Geyser.png | bin | 0 -> 467 bytes | |||
-rw-r--r-- | images/breakables/Haystack.png | bin | 0 -> 579 bytes | |||
-rw-r--r-- | images/breakables/Pumpkin.png | bin | 0 -> 436 bytes | |||
-rw-r--r-- | images/breakables/empty.png | bin | 0 -> 93 bytes | |||
-rw-r--r-- | images/lightning1.png | bin | 12993 -> 0 bytes | |||
-rwxr-xr-x | srb.py | 10 | ||||
-rw-r--r-- | srb_lib.py | 22 | ||||
-rwxr-xr-x | srb_tk.py | 103 |
10 files changed, 117 insertions, 18 deletions
diff --git a/images/breakables/Barrel.png b/images/breakables/Barrel.png Binary files differnew file mode 100644 index 0000000..ba9d3cd --- /dev/null +++ b/images/breakables/Barrel.png diff --git a/images/breakables/Battery.png b/images/breakables/Battery.png Binary files differnew file mode 100644 index 0000000..a6dae8f --- /dev/null +++ b/images/breakables/Battery.png diff --git a/images/breakables/Geyser.png b/images/breakables/Geyser.png Binary files differnew file mode 100644 index 0000000..3bbd4c9 --- /dev/null +++ b/images/breakables/Geyser.png diff --git a/images/breakables/Haystack.png b/images/breakables/Haystack.png Binary files differnew file mode 100644 index 0000000..70eb56e --- /dev/null +++ b/images/breakables/Haystack.png diff --git a/images/breakables/Pumpkin.png b/images/breakables/Pumpkin.png Binary files differnew file mode 100644 index 0000000..c9ded19 --- /dev/null +++ b/images/breakables/Pumpkin.png diff --git a/images/breakables/empty.png b/images/breakables/empty.png Binary files differnew file mode 100644 index 0000000..12fbf84 --- /dev/null +++ b/images/breakables/empty.png diff --git a/images/lightning1.png b/images/lightning1.png Binary files differdeleted file mode 100644 index efbf664..0000000 --- a/images/lightning1.png +++ /dev/null @@ -53,6 +53,7 @@ parser.add_argument("--set-level",action="append",help="Set completion status, b parser.add_argument("--set-level-status",action="append",help="Set completion status for this level for profile. Example value to pass: \"0,general\"") 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("--reset-level-breakables",action="append",help="Reset breakables for these levels for profile. Can be used multiple times.") parser.add_argument("--get-levelset",help="Print status for 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.") @@ -89,7 +90,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_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): +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 or args.reset_level_breakables): ferror("Warning: Cannot perform most actions without --profile. Not all tasks may run.") else: if args.get_money: @@ -260,6 +261,13 @@ else: else: #print(f"good?") srb_lib.write_file(args.file,0,data) + if args.reset_level_breakables: + for l in args.reset_level_breakables: + data, message = srb_lib.set_collected_breakables(args.file,profile_id,l) + if (type(data) == int and data == -1) or message != "": + ferror(f"Failed to reset {profile_id} level {l} breakables because {message}") + else: + 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 completed levels count. @@ -21,7 +21,6 @@ # <https://gamefaqs.gamespot.com/pc/930591-snoopy-vs-the-red-baron/faqs/46161> # Improve: # Rewrite as a class so we do not have to pass data around everywhere? -# Write: set level breakables # Documentation: # winetricks vd=1024x768 # winetricks vd=off @@ -412,6 +411,27 @@ 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_collected_breakables(data_object, profile_id, level, amount = "none"): + """ This resets the collected breakables for a level. Returns silently if given a level that does not have breakables. """ + data = _get_data_from_data_object(data_object) + level_obj, message = get_level_info(level) + if message != "" or level == -1: + return -1, f"Unable to set collected breakables for {level}." + levelset_obj, message = get_levelset_info(level_obj["setid"]) + if message != "" or levelset_obj == -1: + return -1, f"Unable to get levelset info for {level_obj['setid']}." + if not level_obj["r"]: + return data, "" + else: + thisint = 0 if amount == "none" else 5 + thismask = 0 if amount == "none" else [i["p"] for i in BREAKABLES if i["name"] == "all"][0] + 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 + ((-2 * levelset_obj["id"]) + 12) * INT_SIZE + pos_level_which_breakables = pos_level_breakables + INT_SIZE + data = srb_pack("<1I",data,pos_level_breakables,thisint) + data = srb_pack("<1I",data,pos_level_which_breakables,thismask) + return data, "" + 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) @@ -21,8 +21,14 @@ # possible way to have a widget store its variable: https://stackoverflow.com/questions/63871376/tkinter-widget-cgetvariable # count number of 1s in a binary value https://stackoverflow.com/questions/8871204/count-number-of-1s-in-binary-representation/71307775#71307775 # https://stackoverflow.com/questions/67334913/can-i-possibly-put-an-image-label-into-an-option-box-in-tkinter +# haybale <a target="_blank" href="https://icons8.com/icon/8Sk1THlBUpC6/hay-bale">Hay Bale</a> icon by <a target="_blank" href="https://icons8.com">Icons8</a> +# barrel <a target="_blank" href="https://icons8.com/icon/23346/wooden-beer-keg">Wooden Beer Keg</a> icon by <a target="_blank" href="https://icons8.com">Icons8</a> +# geyser <a target="_blank" href="https://icons8.com/icon/IjFwYfQ6Hmgc/geyser">Geyser</a> icon by <a target="_blank" href="https://icons8.com">Icons8</a> +# pumpkin <a target="_blank" href="https://icons8.com/icon/17358/pumpkin">Pumpkin</a> icon by <a target="_blank" href="https://icons8.com">Icons8</a> +# battery <a target="_blank" href="https://icons8.com/icon/dwzqRjO6XcTN/battery">Battery</a> icon by <a target="_blank" href="https://icons8.com">Icons8</a> +# transparent image https://stackoverflow.com/questions/8376359/how-to-create-a-transparent-gif-or-png-with-pil-python-imaging +# combine images https://stackoverflow.com/questions/30227466/combine-several-images-horizontally-with-python/30228308#30228308 # Improve: -# Add reset-level-breakables # Dependencies: # dep-devuan: python3-tkinter, python3-pil.imagetk # rec-devuan: python3-cairosvg @@ -48,13 +54,16 @@ SPDX-License-Identifier: GPL-3.0-only Icon courtesy of https://icons8.com/icon/9336/aircraft """ -def get_srb_image(filename, sizex, sizey, resample=Image.Resampling.LANCZOS): +def get_srb_image(filename, sizex, sizey, resample=Image.Resampling.LANCZOS, photoImage = True): """ helper function that returns an ImageTk from the named file from the configured images directory, resized for you. """ global IMAGE_FAILURES if IMAGES and IMAGE_FAILURES < 5: try: img = Image.open(os.path.join(image_dir,filename)).resize((sizex,sizey),resample=resample) - return ImageTk.PhotoImage(img) + if photoImage: + return ImageTk.PhotoImage(img) + else: + return img except: IMAGE_FAILURES += 1 print(f"Failed to load image {filename}, only {5-IMAGE_FAILURES} failures left before giving up on images entirely.",file=sys.stderr) @@ -147,12 +156,14 @@ class App(tk.Frame): self.level_letters = [] statuses = [i["name"] for i in srb_lib.LEVEL_STATUSES] statuses_hex = [i["b"] for i in srb_lib.LEVEL_STATUSES] + self.level_breakables = [] for i in range(0,len(srb_lib.LEVELS)): self.level_strs.append(tk.StringVar(value=srb_lib.get_level_info(i)[0]["name"])) self.level_statuses_hex.append(tk.StringVar(value=statuses_hex[0])) self.level_statuses.append(tk.StringVar(value=statuses[0])) self.level_balloons.append(tk.IntVar()) self.level_letters.append(tk.IntVar()) + self.level_breakables.append(tk.IntVar()) # END DATA VARIABLES # statusbar variable @@ -253,8 +264,6 @@ class App(tk.Frame): self.spn_gun.grid(row=4,column=1) self.lbl_pur = tk.Label(self.frm_curprof,text="Purchased: Weapons") self.lbl_pur.grid(row=5,column=0) - #self.lbl_pur_weap = tk.Label(self.frm_curprof,text="Weapons") - #self.lbl_pur_weap.grid(row=5,column=0) self.lbl_pur_weap = tk.Label(self.frm_curprof,text="Planes") self.lbl_pur_weap.grid(row=5,column=1) self.img_potato = get_srb_image("Potato Gun.png",16,16) @@ -300,16 +309,12 @@ 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/Completed") - self.lbl_levelsets_name.grid(row=0,column=0,sticky="EW",columnspan=4) - 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") - self.lbl_levels_status.grid(row=0,column=6,sticky="EW",columnspan=2) - self.lbl_levels_balloons = tk.Label(self.frm_levels,text="Balloons") - self.lbl_levels_balloons.grid(row=0,column=8,columnspan=4,sticky="EW") - self.lbl_levels_letters = tk.Label(self.frm_levels,text="Letters") - self.lbl_levels_letters.grid(row=0,column=12,columnspan=4,sticky="EW") + tk.Label(self.frm_levels,text="Billboard/Completed").grid(row=0,column=0,sticky="EW",columnspan=4) + tk.Label(self.frm_levels,text="Level").grid(row=0,column=5,sticky="EW") + tk.Label(self.frm_levels,text="Rank").grid(row=0,column=6,sticky="EW",columnspan=2) + tk.Label(self.frm_levels,text="Balloons").grid(row=0,column=8,columnspan=4,sticky="EW") + tk.Label(self.frm_levels,text="Letters").grid(row=0,column=12,columnspan=4,sticky="EW") + tk.Label(self.frm_levels,text="Breakables").grid(row=0,column=16,columnspan=1,sticky="EW") self.lbl_levels = [] self.opt_levels_hex = [] self.opt_levels = [] @@ -319,8 +324,17 @@ class App(tk.Frame): self.btn_balloons_all = [] self.btn_letters_none = [] self.btn_letters_all = [] - x = 1 + self.btn_levels_breakables = [] + self.bim_levels_breakables = [] self.img_balloons = [] + self.img_breakables = [] + self.BREAKABLES = sorted(srb_lib.BREAKABLES,key=lambda i: i["w"]) + self.BREAKABLES = [b for b in self.BREAKABLES if b["w"] != 0] + self.BREAKABLES_NAMES = [b["name"] for b in self.BREAKABLES if b["w"] != 0] + for i in self.BREAKABLES: + self.img_breakables.append(get_srb_image(os.path.join(image_dir,"breakables",i["name"]+".png"),16,16,photoImage=False)) + self.img_breakables.append(get_srb_image(os.path.join(image_dir,"breakables","empty.png"),16,16,photoImage=False)) + x = 1 for i in range(0,len(srb_lib.LEVELSETS)): tl = srb_lib.get_levelset_info(i)[0] if "c" not in tl or tl["c"] == "none": @@ -371,7 +385,17 @@ class App(tk.Frame): self.btn_letters_none[-1].grid(row=x,column=14) 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=15) + # breakables + if "r" in j and j["r"]: + self.bim_levels_breakables.append(get_srb_image(os.path.join("breakables","empty.png"),16,16)) + # until you apply an image, width of a widget measures approximate width of text characters + self.btn_levels_breakables.append(tk.Button(self.frm_levels,padx=0,pady=0,width=2,compound="left",command=lambda tl=j: self.button_reset_level_breakables(tl))) + if IMAGES: + # and now all of a sudden it does pixels. + self.btn_levels_breakables[-1].config(width=88,image=self.bim_levels_breakables[-1]) + self.btn_levels_breakables[-1].grid(row=x,column=16) x += 1 + # end for-j-in-levels x += 1 # end for-i-in-levelsets @@ -381,6 +405,22 @@ class App(tk.Frame): # and now open the first file self.func_open(self.current_file.get()) + def get_single_image_from_breakables(self,b_list): + """ Given a list of breakable names, return a single image that has those items displayed. Fixed size of 80px x 16px. """ + #print(f"debug: got list of {b_list}") + if IMAGES: + new_im = Image.new("RGBA",(16*5,16),(255,127,127,0)) + x_offset = 0 + for i in self.BREAKABLES_NAMES: + using = i in b_list + #print(f"For {i}, using {using}, index is {self.BREAKABLES_NAMES.index(i)}") + this_img = self.img_breakables[self.BREAKABLES_NAMES.index(i)] if i in b_list else self.img_breakables[-1] + new_im.paste(this_img, (x_offset,0)) + x_offset += this_img.size[0] + return ImageTk.PhotoImage(new_im) + else: + return -1 + def set_frame_backgrounds(self, arg1 = None, arg2 = None, arg3 = None): """ Reacts to the frame colors checkbox and sets frames which make development and layout easier. """ if self.frame_backgrounds.get(): @@ -393,6 +433,18 @@ class App(tk.Frame): self.frm_curprof.config(background=bg,borderwidth=0) self.frm_levels.config(background=bg,borderwidth=0) + def button_reset_level_breakables(self, level_obj): + """ Handle button press to reset breakables for a level. """ + profile_id = self.selected_profile.get() + _, b_mask = srb_lib.get_collected_breakables(self.bdata,profile_id,level_obj,silent=True) + amount = "all" if 0 == b_mask else "none" + bdata, message = srb_lib.set_collected_breakables(self.bdata,profile_id,level_obj,amount) + if -1 == bdata or message != "": + raise Exception("Unable to reset breakables for level {level_obj} because {message}") + else: + self.bdata = bdata + self.load_form_into_data() + 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_}") @@ -623,6 +675,7 @@ class App(tk.Frame): if not self.bdata: return "No file loaded." profile_id = self.selected_profile.get() + y = 0 if profile_id >= 1 and profile_id <= 3: self.mark_traces(False) # money @@ -690,6 +743,7 @@ class App(tk.Frame): self.levelset_status_ints[x].set(mission_mask) self.levelset_balloons_ints[x].set(balloon_count) x += 1 + # end for-in-in-levelsets # levels for i in range(0,len(srb_lib.LEVELS)): this_level = [j for j in srb_lib.LEVELS if j["id"] == i][0] @@ -731,6 +785,23 @@ class App(tk.Frame): # 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) + if this_level["r"]: + b_str, b_mask = srb_lib.get_collected_breakables(self.bdata,profile_id,this_level,silent=True) + if "all" == b_str: + b_str = ','.join([b["name"] for b in self.BREAKABLES]) + b_list = b_str.split(",") + if "none" in b_list: + b_list.pop(b_list.index("none")) + #print(f"debug: for {this_level['name']}, i {i}, y {y}, got b {b_list}, mask {b_mask}") + self.bim_levels_breakables[y] = self.get_single_image_from_breakables(b_list) + if self.bim_levels_breakables[y] != -1: # which really means, if IMAGES + self.btn_levels_breakables[y].config(image=self.bim_levels_breakables[y]) + self.btn_levels_breakables[y].config(text=str(len(b_list))) + # y counts number of levels with breakables + y = y + 1 + # end of if-level-has-breakables + + # end for-i-in-levels # end if-profile_id is 1,2 or 3. # mark-traces-enable must happen at the end! self.mark_traces(True) |