From 272d7202b0d280143190a1e9989529a7b8c7c76b Mon Sep 17 00:00:00 2001 From: Luxferre Date: Sat, 12 Oct 2024 13:08:55 +0300 Subject: [PATCH] initial upload --- README.md | 33 ++ armagedec.tcl | 815 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 848 insertions(+) create mode 100644 README.md create mode 100755 armagedec.tcl diff --git a/README.md b/README.md new file mode 100644 index 0000000..653f1c7 --- /dev/null +++ b/README.md @@ -0,0 +1,33 @@ +# ArmageDec: a free and cross-platform game scheme editor for Worms: Armageddon .wsc files + +ArmageDec (short for Armageddon Decoder) is a GUI scheme editor (written in Tcl/Tk 8.6) for Worms: Armageddon game scheme file format from version 1 to version 3. It was developed based on [this format description page](https://worms2d.info/Game_scheme_file) and supports all fields readable as of W:A v3.8.1, including those not present in some other external scheme editors. + +## Usage + +ArmageDec is a fully GUI-based application, although you can pass the .wsc scheme file to open as the first command-line parameter to the script. You can modify all supported parameters, including the format version itself. If you supply an invalid version value, the program will assume and save version 3. + +Note that you can't create a fully new scheme as of now. You need to open an existing .wsc file and start by editing it. + +## Binary builds + +It is possible (although absolutely unnecessary) to build ArmageDec as a stand-alone binary. For Windows users, binary builds will be provided along with the source code on the "Releases" tab of the repo. + +To build a binary distribution of ArmageDec yourself, follow these steps: + +1. Obtain the tclkits for your host and target platforms, e.g. build them [here](https://kitcreator.rkeene.org/kitcreator). +Make sure you're building the target tclkit with Metakit and Tk packages enabled. +If the host and target platform are the same, you still need two kit binaries (just make a copy). + +2. Download `sdx.kit` from somewhere, e.g. from [here](https://codeberg.org/luxferre/StreamGoose/raw/branch/main/tcldep/sdx.kit) + +3. Put the two binaries and `sdx.kit` into the same directory as `armagedec.tcl`, then run from this directory: +``` +[/path/to/host/tclkit] sdx.kit qwrap armagedec.tcl -runtime [/path/to/target/tclkit] +``` +A file just named `armagedec` should appear, this is your executable. + +4. If building for Windows, rename `armagedec` to `armagedec.exe`. + +## Credits + +Created by Luxferre in 2024, released into public domain. diff --git a/armagedec.tcl b/armagedec.tcl new file mode 100755 index 0000000..599ab73 --- /dev/null +++ b/armagedec.tcl @@ -0,0 +1,815 @@ +#!/usr/bin/env tclsh +# ArmageDec (Armageddon Decoder): a free and cross-platform +# game scheme editor for Worms: Armageddon .wsc files +# (supports format versions 1 to 3) +# Depends on Tk only +# Created by Luxferre in 2024, released into public domain + +package require Tk + +set appversion 0.1 +set scriptpath [file normalize [info script]] +set appdir [file dirname $scriptpath] +# check if we're running from a starpack +if [string match *app-armagedec $appdir] { + set appdir [file normalize [file join $appdir ".." ".." ".."]] +} + +# scheme manipulation routines +set rawschemedata {} +array set wsc_data {} + +# ordered list of options, all 8-bit integers +# val can be empty or dict or start with bool or spec +# if starts with spec, then it invokes special calculation proc +# with the corresponding name +set wa_opts { + {offset 0x05 name {Hot-Seat Delay (seconds)} val {}} + {offset 0x06 name {Retreat Time (seconds)} val {}} + {offset 0x07 name {Rope Retreat Time (seconds)} val {}} + {offset 0x0A name {Fall Damage} val {}} + {offset 0x10 name {Water Rise Rate} val {spec wa_waterrise}} + {offset 0x11 name {Weapon Crate Probability} val {}} + {offset 0x13 name {Health Crate Probability} val {}} + {offset 0x14 name {Health Crate Energy (HP per box)} val {}} + {offset 0x15 name {Utility Crate Probability} val {}} + {offset 0x16 name {Hazardous Object Specification} val {}} + {offset 0x17 name {Mine Delay} val {spec wa_minedelay}} + {offset 0x1A name {Initial Worm Energy (HP)} val {}} + {offset 0x1B name {Turn Time (seconds)} val {}} + {offset 0x1C name {Time Before Sudden Death} val {spec wa_roundtime}} + {offset 0x1D name {Rounds to Win} val {}} + {offset 0x0B name {Artillery Mode (anchor)} val {bool}} + {offset 0x08 name {Display Total Round Time} val {bool}} + {offset 0x09 name {Automatic Replays} val {bool}} + {offset 0x12 name {Donor Cards} val {bool}} + {offset 0x18 name {Dud Mines} val {bool}} + {offset 0x19 name {Manual Worm Placement} val {bool}} + {offset 0x1F name {Aqua Sheep} val {bool}} + {offset 0x20 name {Sheep Heaven} val {bool}} + {offset 0x21 name {Infinite HP} val {bool}} + {offset 0x22 name {Indestructible Land} val {bool}} + {offset 0x27 name {Team Weapons} val {bool}} + {offset 0x28 name {Super Weapons} val {bool}} + {offset 0x1E name {Blood} val {bool}} + {offset 0x23 name {Upgraded Grenade} val {bool}} + {offset 0x24 name {Upgraded Shotgun} val {bool}} + {offset 0x0F name {Sudden Death Event} val {0 {Round ends} 1 {Nuclear strike} 2 {HP drops to 1} 3 {Nothing happens}}} + {offset 0x0D name {Stockpiling Mode} val {0 Replenish 1 Accumulate 2 Reduce}} + {offset 0x0E name {Worm Selection} val {0 Sequential 1 Manual 2 Random}} + {offset 0x25 name {Upgraded Clusters} val {bool}} + {offset 0x26 name {Upgraded Longbow} val {bool}} +} + +# names of weapon properties +set wa_weap_props {Ammunition Power Delay Probability} + +# ordered list of weapons +set wa_weapons { + {offset 0x29 name {Bazooka} maxpower 20} + {offset 0x2D name {Homing Missile} maxpower 20} + {offset 0x31 name {Mortar} maxpower 15} + {offset 0x35 name {Grenade} maxpower 20} + {offset 0x39 name {Cluster Bomb} maxpower 15} + {offset 0x3D name {Skunk} maxpower 10} + {offset 0x41 name {Petrol Bomb} maxpower 20} + {offset 0x45 name {Banana Bomb} maxpower 15} + {offset 0x49 name {Handgun} maxpower 20} + {offset 0x4D name {Shotgun} maxpower 20} + {offset 0x51 name {Uzi} maxpower 20} + {offset 0x55 name {Minigun} maxpower 20} + {offset 0x59 name {Longbow} maxpower 10} + {offset 0x5D name {Airstrike} maxpower 15} + {offset 0x61 name {Napalm Strike} maxpower 15} + {offset 0x65 name {Mine} maxpower 20} + {offset 0x69 name {Fire Punch} maxpower 20} + {offset 0x6D name {Dragon Ball} maxpower 20} + {offset 0x71 name {Kamikaze} maxpower 20} + {offset 0x75 name {Prod} maxpower 20} + {offset 0x79 name {Battle Axe} maxpower 5} + {offset 0x7D name {Blowtorch} maxpower 20} + {offset 0x81 name {Pneumatic Drill} maxpower 20} + {offset 0x85 name {Girder} maxpower {}} + {offset 0x89 name {Ninja Rope} maxpower {}} + {offset 0x8D name {Parachute} maxpower {}} + {offset 0x91 name {Bungee} maxpower {}} + {offset 0x95 name {Teleport} maxpower 20} + {offset 0x99 name {Dynamite} maxpower 20} + {offset 0x9D name {Sheep} maxpower 20} + {offset 0xA1 name {Baseball Bat} maxpower 20} + {offset 0xA5 name {Flame Thrower} maxpower 20} + {offset 0xA9 name {Homing Pigeon} maxpower 20} + {offset 0xAD name {Mad Cow} maxpower 20} + {offset 0xB5 name {Old Woman} maxpower 20} + {offset 0xB9 name {Sheep Launcher} maxpower 20} + {offset 0xBD name {Super Sheep} maxpower 20} + {offset 0xC1 name {Mole Bomb} maxpower 20} + {offset 0xC5 name {Jet Pack} maxpower {}} + {offset 0xC9 name {Low Gravity} maxpower {}} + {offset 0xCD name {Laser Sight} maxpower {}} + {offset 0xD1 name {Fast Walk} maxpower {}} + {offset 0xD5 name {Invisibility} maxpower {}} + {offset 0xD9 name {Damage x2} maxpower {}} + {offset 0xB1 name {Holy Hand Grenade} maxpower 20} + {offset 0xDD name {Freeze} maxpower {}} + {offset 0xE1 name {Super Banana Bomb} maxpower {}} + {offset 0xE5 name {Mine Strike} maxpower {}} + {offset 0xE9 name {Girder Starter Pack} maxpower {}} + {offset 0xED name {Earthquake} maxpower {}} + {offset 0xF1 name {Scales Of Justice} maxpower {}} + {offset 0xF5 name {Ming Vase} maxpower {}} + {offset 0xF9 name {Mike's Carpet Bomb} maxpower {}} + {offset 0xFD name {Patsy's Magic Bullet} maxpower {}} + {offset 0x101 name {Indian Nuclear Test} maxpower {}} + {offset 0x105 name {Select Worm} maxpower {}} + {offset 0x109 name {Salvation Army} maxpower {}} + {offset 0x10D name {Mole Squadron} maxpower {}} + {offset 0x111 name {MB Bomb} maxpower {}} + {offset 0x115 name {Concrete Donkey} maxpower {}} + {offset 0x119 name {Suicide Bomber} maxpower {}} + {offset 0x11D name {Sheep Strike} maxpower {}} + {offset 0x121 name {Mail Strike} maxpower {}} + {offset 0x125 name {Armageddon} maxpower {}} +} + +# ordered list of extended options (v3 schemes) +# can be larger size or non-int type, also has default values +# val can be empty or dict or start with bool or fixp or spec +# if starts with spec, then it invokes special calculation proc +# with the corresponding name +set wa_ext_opts { + {offset 0x12E size 2 name {Wind} def 100 val {}} + {offset 0x130 size 1 name {Wind Bias} def 15 val {}} + {offset 0x131 size 4 name {Gravity} def 0x3D70 val {fixp}} + {offset 0x135 size 4 name {Friction} def 0xF5C2 val {fixp}} + {offset 0x139 size 1 name {Rope Knocking Force} def 255 val {}} + {offset 0x13A size 1 name {Blood Level} def 255 val {}} + {offset 0x13D size 1 name {No-Crate Probability} def 255 val {}} + {offset 0x13E size 2 name {Maximum Crate Count at Once} def 5 val {}} + {offset 0x141 size 1 name {Sudden Death Damage Per Turn} def 5 val {}} + {offset 0x196 size 1 name {Double Time Stack Limit} def 1 val {}} + {offset 0x158 size 2 name {Petrol Turn Decay} def 0x3332 val {spec wa_petrolturndecay}} + {offset 0x15A size 1 name {Petrol Touch Decay} def 30 val {}} + {offset 0x15B size 2 name {Maximum Flamelet Count} def 200 val {}} + {offset 0x15D size 4 name {Maximum Projectile Speed} def 0x200000 val {fixp}} + {offset 0x161 size 4 name {Maximum Rope Speed} def 0x100000 val {fixp}} + {offset 0x165 size 4 name {Maximum Jet Pack Speed} def 0x50000 val {fixp}} + {offset 0x169 size 4 name {Game Engine Speed} def 0x10000 val {fixp}} + {offset 0x175 size 4 name {Bounciness} def 0 val {fixp}} + {offset 0x179 size 4 name {Air Viscosity} def 0 val {fixp}} + {offset 0x17E size 4 name {Wind Influence} def 0 val {fixp}} + {offset 0x184 size 4 name {RubberWorm Gravity Strength} def 0x10000 val {fixp}} + {offset 0x188 size 1 name {Crate Rate} def 0 val {}} + {offset 0x12D size 1 name {Constant Wind} def 0 val {bool}} + {offset 0x13B size 1 name {Unrestrict Rope} def 0 val {bool}} + {offset 0x13C size 1 name {Group Worms by Ally} def 0 val {bool}} + {offset 0x140 size 1 name {Sudden Death Disables Worm Select} def 1 val {bool}} + {offset 0x144 size 1 name {Circular Aim} def 0 val {bool}} + {offset 0x145 size 1 name {Anti-Lock Aim} def 0 val {bool}} + {offset 0x146 size 1 name {Anti-Lock Power} def 0 val {bool}} + {offset 0x147 size 1 name {Worm Selection Doesn't End Hot Seat} def 0 val {bool}} + {offset 0x148 size 1 name {Worm Selection Is Never Cancelled} def 0 val {bool}} + {offset 0x149 size 1 name {Infinite Rope/Bungee/Jetpack} def 0 val {bool}} + {offset 0x194 size 1 name {Conserve Instant Utilities} def 0 val {bool}} + {offset 0x195 size 1 name {Expedite Instant Utilities} def 0 val {bool}} + {offset 0x14C size 1 name {Keep Control After Bumping Head} def 0 val {bool}} + {offset 0x14E size 1 name {Explosions Trigger Fall Damage} def 0 val {bool}} + {offset 0x152 size 1 name {Pause Timer While Firing} def 1 val {bool}} + {offset 0x153 size 1 name {Loss of Control Doesn't End Turn} def 0 val {bool}} + {offset 0x154 size 1 name {Weapon Use Doesn't End Turn} def 0 val {bool}} + {offset 0x155 size 1 name {WDET Doesn't Block Anything} def 0 val {bool}} + {offset 0x157 size 1 name {Girder Radius Assist} def 0 val {bool}} + {offset 0x16F size 1 name {Jet Pack Bungee Glitch} def 1 val {bool}} + {offset 0x170 size 1 name {Angle Cheat Glitch} def 1 val {bool}} + {offset 0x171 size 1 name {Glide Glitch} def 1 val {bool}} + {offset 0x174 size 1 name {Floating Weapon Glitch} def 1 val {bool}} + {offset 0x189 size 1 name {Crate Shower} def 0 val {bool}} + {offset 0x18A size 1 name {Anti-Sink} def 0 val {bool}} + {offset 0x18B size 1 name {Remember Weapons} def 0 val {bool}} + {offset 0x18C size 1 name {Extended Fuses/Herds} def 0 val {bool}} + {offset 0x18D size 1 name {Anti-Lock Aim} def 0 val {bool}} + {offset 0x18F size 1 name {Fractional Round Timer} def 0 val {bool}} + {offset 0x190 size 1 name {Automatic End-of-Turn Retreat} def 0 val {bool}} + {offset 0x17D size 1 name {Air Viscosity Also Applies to Worms} def 0 val {bool}} + {offset 0x182 size 1 name {Wind Influence Also Applies to Worms} def 0 val {bool}} + {offset 0x142 size 1 name {Phased Worms (Allied)} def 0 val { + 0 {Off} + 1 {Worms move through each other} + 2 {+ Move through allied weapon objects} + 3 {+ Don't take damage from allied explosions} + }} + {offset 0x14F size 1 name {Explosions Push All Objects} def 128 val {0 No 1 Yes 128 Default}} + {offset 0x150 size 1 name {Undetermined Crates} def 128 val {0 Off 1 On 128 Default}} + {offset 0x151 size 1 name {Undetermined Fuses} def 128 val {0 Off 1 On 128 Default}} + {offset 0x14A size 1 name {Rope-Roll Drops} def 0 val {0 {Disabled} 1 {As from rope only} 2 {As from rope or jump}}} + {offset 0x14B size 1 name {Horizontal Impact Loss of Control} def 0 val {0 {Lose control} 255 {No loss of control}}} + {offset 0x143 size 1 name {Phased Worms (Enemy)} def 0 val { + 0 {Off} + 1 {Worms move through each other} + 2 {+ Move through enemy weapon objects} + 3 {+ Don't take damage from enemy explosions} + }} + {offset 0x14D size 1 name {Keep Control After Skimming} def 0 val {0 {Lose control} 1 {Keep control} 2 {Keep control and rope}}} + {offset 0x156 size 1 name {Pneumatic Drill Imparts Velocity} def 128 val {0 Off 1 On 128 Default}} + {offset 0x16D size 1 name {Indian Rope Glitch} def 128 val {0 Off 1 On 128 Default}} + {offset 0x16E size 1 name {Herd-Doubling Glitch} def 128 val {0 Off 1 On 128 Default}} + {offset 0x172 size 1 name {Skipwalking} def 0 val {255 Disabled 0 Possible 1 Facilitated}} + {offset 0x193 size 1 name {Sheep Heaven's Gate} def 7 val { + 1 {1. Sheep explode out of all crates} + 2 {2. Sheep weapon extended fuse time} + 3 {3. Option 1 + Option 2} + 4 {4. Sheep weapon boosted probabilities} + 5 {5. Option 1 + Option 4} + 6 {6. Option 2 + Option 4} + 7 {7. All of the above} + }} + {offset 0x173 size 1 name {Roofing} def 0 val {0 {Allowed} 1 {Block above} 2 {Block everywhere}}} + {offset 0x18E size 1 name {Terrain Overlap Phasing Glitch} def 128 val {0 Off 1 On 128 Default}} + {offset 0x183 size 1 name {RubberWorm Gravity Type} def 0 val {0 {Unmodified} 1 {Standard} 2 {Black hole (constant)} 3 {Black hole (linear)}}} + {offset 0x191 size 1 name {Health Crates Cure Poison} def 1 val {255 {Off} 0 {Cure the collector} 2 {Cure the collector's team} 3 {Cure all allied teams}}} + {offset 0x192 size 1 name {Kaos Mod Preset} def 0 val { + 0 {Preset 0} + 1 {Preset 1} + 2 {Preset 2} + 3 {Preset 3} + 4 {Preset 4} + 5 {Preset 5} + }} +} + +# special setting processor section +# every special processor spec contains 2 functions +# spec_read consumes the raw byte sequence and converts it into the Tcl-native number +# spec_write consumes the Tcl-native number and converts it into the byte sequence + +# all versions spec functions + +# water rise rate (flooding speed) +proc wa_waterrise_read {raw} { + return [expr {(($raw * $raw) * 5) % 256}] +} +set wa_water_table {} +for {set i 0} {$i < 256} {incr i} { + set wval [wa_waterrise_read $i] + if {![dict exists $wa_water_table $wval]} { + dict set wa_water_table $wval $i + } +} +proc wa_waterrise_write {num} { + global wa_water_table + # if the value we want to save doesn't match the table then we save 0 + if {[dict exists $wa_water_table $num]} { + return [dict get $wa_water_table $num] + } else {return 0} +} + +# mine delay (in seconds) +proc wa_minedelay_read {raw} { + if {($raw eq 4) || ($raw > 0x7F)} {return "random"} else {return $raw} +} +proc wa_minedelay_write {num} { + if {$num eq "random"} {return 4} else {return $num} +} + +# the amount of time before sudden death is triggered +# these functions convert everything to seconds and back +# TODO rigorously test this +proc wa_roundtime_read {raw} { + set sec 0 + if {$raw < 0x80} { + set sec [expr {$raw * 60}] + } else { + set sec [expr {$raw - 0x80}] + } + return $sec +} +proc wa_roundtime_write {num} { + if {$num > 60} { + return [expr {int($num / 60)}] + } elseif {$num > 0} { + return [expr {$num + 0x80}] + } else {return 0} +} + +# fixedpoint helpers +proc fixed2float {fpint} { + set whole [expr {int($fpint) >> 16}] + set frac [expr {int($fpint) & 65535}] + return [expr {$whole + ($frac / 65536)}] +} + +proc float2fixed {flt} { + set whole [expr {int(float($flt))}] + set frac [expr {float($flt) - $whole}] + return [expr {($whole << 16) | int($frac * 65536)}] +} + +# v3-only spec functions + +proc wa_petrolturndecay_read {raw} {return [fixed2float $raw]} +proc wa_petrolturndecay_write {num} {return [expr int($num * 65536)]} + +# general logic + +# scan and load all existing fields in the scheme data +proc fullscanscheme {version} { + global rawschemedata wsc_data wa_opts wa_weapons wa_ext_opts + array set wsc_data {} + set wsc_data(version) $version + # scan normal options + foreach opt $wa_opts { + set offs [dict get $opt offset] + set offs [expr {int($offs)}] + set valopt [dict get $opt val] + set scanval [string index $rawschemedata $offs] + binary scan $scanval cu wscval + if {([llength $valopt] > 1) && ([lindex $valopt 0] eq "spec")} { + set readfunc "[lindex $valopt 1]_read" + set wscval [$readfunc $wscval] + } + set wsc_data($offs) $wscval + } + # scan weapons + foreach weap $wa_weapons { + set offs [dict get $weap offset] + set offs [expr {int($offs)}] + # limit weapons for v1 schemes + if {($offs >= 0xDD) && ($version < 2)} {break} + set poweroffs [expr {$offs + 1}] + set delayoffs [expr {$offs + 2}] + set proboffs [expr {$offs + 3}] + binary scan [string range $rawschemedata $offs [expr {$offs + 4}]] cucucucu ammo power delay prob + set wsc_data($offs) $ammo + set wsc_data($poweroffs) $power + set wsc_data($delayoffs) $delay + set wsc_data($proboffs) $prob + } + # scan extended options + foreach opt $wa_ext_opts { + set offs [dict get $opt offset] + set offs [expr {int($offs)}] + set size [dict get $opt size] + set size [expr {int($size)}] + set wscval [dict get $opt def] + set valopt [dict get $opt val] + if {$size eq 1} {set fmt cu} + if {$size eq 2} {set fmt s} + if {$size eq 4} {set fmt i} + catch { + binary scan [string range $rawschemedata [expr {$offs + $size}]] $fmt wscval + } + if {([llength $valopt] > 1) && ([lindex $valopt 0] eq "spec")} { + set readfunc "[lindex $valopt 1]_read" + set wscval [$readfunc $wscval] + } elseif {$valopt eq "fixp"} { + set wscval [fixed2float $wscval] + } + set wsc_data($offs) $wscval + } +} + +# load a scheme file into the internal structure +proc loadscheme {fname} { + global rawschemedata wsc_data + set fp [open $fname r] + fconfigure $fp -translation binary + set rawschemedata [read $fp] + close $fp + set hdr "" + set ver "" + binary scan $rawschemedata a4c hdr ver + if {$hdr eq {SCHM} && ($ver eq 1 || $ver eq 2 || $ver eq 3)} { + # proceed with full scanning + fullscanscheme $ver + } else { + tk_messageBox -type ok -title "Scheme loading error" -icon error -message "Invalid scheme header or version!" + set rawschemedata {} + } +} + +# save a scheme file from the internal structure +proc savescheme {fname} { + global rawschemedata wsc_data wa_opts wa_weapons wa_ext_opts + # guard the version + if {int($wsc_data(version)) < 1} {set wsc_data(version) 1} + if {int($wsc_data(version)) > 3} {set wsc_data(version) 3} + # write normal options + foreach opt $wa_opts { + set offs [dict get $opt offset] + set offs [expr {int($offs)}] + set valopt [dict get $opt val] + set val $wsc_data($offs) + if {([llength $valopt] > 1) && ([lindex $valopt 0] eq "spec")} { + set writefunc "[lindex $valopt 1]_write" + set val [$writefunc $val] + } + set binval [binary format cu $val] + set rawschemedata [string replace $rawschemedata $offs $offs $binval] + } + # scan weapons + foreach weap $wa_weapons { + set offs [dict get $weap offset] + set offs [expr {int($offs)}] + # limit weapons for v1 schemes + if {($offs >= 0xDD) && ($wsc_data(version) < 2)} {break} + set poweroffs [expr {$offs + 1}] + set delayoffs [expr {$offs + 2}] + set proboffs [expr {$offs + 3}] + set ammo $wsc_data($offs) + set power $wsc_data($poweroffs) + set delay $wsc_data($delayoffs) + set prob $wsc_data($proboffs) + set binval [binary format cucucucu $ammo $power $delay $prob] + set rawschemedata [string replace $rawschemedata $offs $proboffs $binval] + } + # for v3, write extended options + if {$wsc_data(version) > 2} { + foreach opt $wa_ext_opts { + set offs [dict get $opt offset] + set offs [expr {int($offs)}] + set size [dict get $opt size] + set size [expr {int($size)}] + set val $wsc_data($offs) + set valopt [dict get $opt val] + if {$size eq 1} {set fmt cu} + if {$size eq 2} {set fmt s} + if {$size eq 4} {set fmt i} + if {([llength $valopt] > 1) && ([lindex $valopt 0] eq "spec")} { + set writefunc "[lindex $valopt 1]_read" + set val [$writefunc $val] + } elseif {$valopt eq "fixp"} { + set val [float2fixed $val] + } + set binval [binary format $fmt $val] + set wsc_data($offs) $wscval + set rawschemedata [string replace $rawschemedata $offs [expr {$offs + $size - 1}] $binval] + } + } + if {$rawschemedata ne ""} { + set fp [open $fname w] + fconfigure $fp -translation binary + puts -nonewline $fp $rawschemedata + close $fp + } +} + +# open a .wsc scheme file (get the target name) +proc openwsc {} { + global wscfile appdir + set startdir $appdir + if {$wscfile ne ""} {set startdir [file dirname $wscfile]} + set types { + {{Worms: Armageddon game scheme} {.wsc} BINA} + } + return [tk_getOpenFile -filetypes $types -initialdir $startdir -title {Open a Worms scheme file}] +} + +# save a .wsc scheme file (get the target name) +proc savewsc {} { + global wscfile + set types { + {{Worms: Armageddon game scheme} {.wsc} BINA} + } + return [tk_getSaveFile -confirmoverwrite true -initialfile $wscfile -filetypes $types -title {Save a Worms scheme file}] +} + +# decode object count value (for mines & barrels calc) +set wa_ho_dlist {1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 35 40 45 50 55 60 65 70 75 80 85 90 95 100 110 120 130 140 150 160 170 180 190 200 210 220 230 240 250} +proc decode_hobj_count {encval} { + global wa_ho_dlist + if {$encval < 8} {return 8} + set m [expr {int($encval) % 4}] + set n [expr {int($encval) - (8 + $m)}] + set e [expr {int($n / 4) - 1}] + return [lindex $wa_ho_dlist $e] +} + +# entry point + +set wscfile "" +if {$argc > 0} { # get the level set file name + set wscfile [lindex $argv 0] +} else { # open the file dialog + set wscfile [openwsc] +} + +if {$wscfile ne ""} {loadscheme $wscfile} + +wm title . "ArmageDec v$appversion" +wm minsize . 640 480 +. configure -menu .mbar +menu .mbar +menu .mbar.fl -tearoff 0 +.mbar add cascade -menu .mbar.fl -label File -underline 0 +.mbar.fl add command -label Open -underline 0 -command { + set wscfile [openwsc] + if {$wscfile ne ""} {loadscheme $wscfile} +} +.mbar.fl add command -label Save -underline 0 -command { + set newwscfile [savewsc] + if {$newwscfile ne ""} { + set wscfile $newwscfile + savescheme $wscfile + } +} +.mbar.fl add command -label Quit -underline 0 -command {exit} +ttk::notebook .tabbar +grid .tabbar -row 0 -column 0 -sticky nswe +grid columnconfigure . 0 -weight 1 +grid rowconfigure . 0 -weight 1 +ttk::frame .infoframe +ttk::label .infoframe.verlabel -text "Scheme file version:" +ttk::label .infoframe.ver -textvariable wsc_data(version) +ttk::label .infoframe.author -text "ArmageDec v$appversion by Luxferre, 2024" +pack .infoframe.verlabel -side left +pack .infoframe.ver -side left +pack .infoframe.author -side right +grid .infoframe -row 1 -column 0 -sticky we + +# Normal options +ttk::frame .tabbar.nopts +.tabbar add .tabbar.nopts -text "Options" +ttk::labelframe .tabbar.nopts.opts -text "General options" +grid .tabbar.nopts.opts -column 0 -row 0 -sticky nw -padx 5 -pady 5 -columnspan 6 + +set row 0 +set col 0 +foreach opt $wa_opts { + set offs [dict get $opt offset] + set offs [expr {int($offs)}] + set boundvar "wsc_data($offs)" + set name [dict get $opt name] + set valopt [dict get $opt val] + # create a different type of option element depending on valopt + if {$valopt eq "" || ([lindex $valopt 0] eq "spec")} { # create a normal text entry + set elpref ".tabbar.nopts.opts.val$offs" + ttk::frame $elpref + set lblid [string cat $elpref ".lbl"] + set txtid [string cat $elpref ".txt"] + ttk::label $lblid -text $name + ttk::entry $txtid -width 8 -textvariable $boundvar + pack $lblid -side left -fill x -padx 5 + pack $txtid -side right + grid $elpref -column $col -row $row -sticky nwe + } elseif {([lindex $valopt 0] eq "bool")} { # create a checkbox entry + set chkid ".tabbar.nopts.opts.chk$offs" + ttk::checkbutton $chkid -text $name -variable $boundvar -onvalue 1 -offvalue 0 + grid $chkid -column $col -row $row -sticky nw + } else { # attempt to read the valopt as dictionary and create a radiobutton set + set comboid ".tabbar.nopts.opts.cmb$offs" + ttk::labelframe $comboid -text $name -labelanchor nw + set irow 0 + dict for {rval dval} $valopt { + set rbid [string cat $comboid "." $rval] + ttk::radiobutton $rbid -variable $boundvar -text $dval -value $rval + grid $rbid -column 0 -row $irow -sticky nswe + incr irow + } + grid $comboid -column $col -row $row -sticky news + } + incr col + if {$col > 4} { + set col 0 + incr row + } +} + +# Weapons +ttk::frame .tabbar.weap +.tabbar add .tabbar.weap -text "Weapons" + +set row 0 +set col 0 +foreach weap $wa_weapons { + set offs [dict get $weap offset] + set offs [expr {int($offs)}] + set wname [dict get $weap name] + set poweroffs [expr {$offs + 1}] + set delayoffs [expr {$offs + 2}] + set proboffs [expr {$offs + 3}] + set boundvar_ammo "wsc_data($offs)" + set boundvar_power "wsc_data($poweroffs)" + set boundvar_delay "wsc_data($delayoffs)" + set boundvar_prob "wsc_data($proboffs)" + set groupid ".tabbar.weap.$offs" + ttk::labelframe $groupid -text $wname -labelanchor nw + ttk::label "$groupid.ammolbl" -text "Ammo" + ttk::entry "$groupid.ammotxt" -textvariable $boundvar_ammo -width 4 + grid "$groupid.ammolbl" -column 0 -row 0 -sticky nsw + grid "$groupid.ammotxt" -column 1 -row 0 -sticky nw + ttk::label "$groupid.powerlbl" -text "Power" + ttk::entry "$groupid.powertxt" -textvariable $boundvar_power -width 4 + grid "$groupid.powerlbl" -column 0 -row 1 -sticky nsw + grid "$groupid.powertxt" -column 1 -row 1 -sticky nw + ttk::label "$groupid.delaylbl" -text "Delay" + ttk::entry "$groupid.delaytxt" -textvariable $boundvar_delay -width 4 + grid "$groupid.delaylbl" -column 0 -row 2 -sticky nsw + grid "$groupid.delaytxt" -column 1 -row 2 -sticky nw + ttk::label "$groupid.problbl" -text "Probability" + ttk::entry "$groupid.probtxt" -textvariable $boundvar_prob -width 4 + grid "$groupid.problbl" -column 0 -row 3 -sticky nsw + grid "$groupid.probtxt" -column 1 -row 3 -sticky nw + grid $groupid -column $col -row $row -sticky news -padx 5 -pady 3 + incr col + if {$col > 10} { + set col 0 + incr row + } +} + +# Extended options tabframe +ttk::frame .tabbar.extopts +.tabbar add .tabbar.extopts -text "Extended options (v3)" + +# Extended option checkboxes in the first tab +set eflpref ".tabbar.nopts.eflags" +ttk::labelframe $eflpref -text "Extended option flags (v3)" +grid $eflpref -row 1 -column 0 -sticky nw -padx 5 -pady 5 -columnspan 8 + +# Extended option textboxes in the first tab +set etpref ".tabbar.nopts.etxts" +ttk::labelframe $etpref -text "Extended option parameters (v3)" +grid $etpref -row 2 -column 0 -sticky nw -padx 5 -pady 5 -columnspan 8 + +set row 0 +set col 0 +set chkrow 0 +set chkcol 0 +set txtrow 0 +set txtcol 0 +foreach opt $wa_ext_opts { + set offs [dict get $opt offset] + set offs [expr {int($offs)}] + set boundvar "wsc_data($offs)" + set name [dict get $opt name] + set valopt [dict get $opt val] + # create a different type of option element depending on valopt + if {$valopt eq "" || ([lindex $valopt 0] eq "spec") || ([lindex $valopt 0] eq "fixp")} { # create a normal text entry + set elpref ".tabbar.nopts.etxts.val$offs" + ttk::frame $elpref + set lblid [string cat $elpref ".lbl"] + set txtid [string cat $elpref ".txt"] + ttk::label $lblid -text $name + ttk::entry $txtid -width 8 -textvariable $boundvar + pack $lblid -side left -fill x -padx 5 + pack $txtid -side right + grid $elpref -column $txtcol -row $txtrow -sticky nwe + incr txtcol + if {$txtcol > 5} { + set txtcol 0 + incr txtrow + } + } elseif {([lindex $valopt 0] eq "bool")} { # create a checkbox entry + set chkid ".tabbar.nopts.eflags.chk$offs" + ttk::checkbutton $chkid -text $name -variable $boundvar -onvalue 1 -offvalue 0 + grid $chkid -column $chkcol -row $chkrow -sticky nw + incr chkcol + if {$chkcol > 4} { + set chkcol 0 + incr chkrow + } + } else { # attempt to read the valopt as dictionary and create a radiobutton set + set comboid ".tabbar.extopts.cmb$offs" + ttk::labelframe $comboid -text $name -labelanchor nw + set irow 0 + dict for {rval dval} $valopt { + set rbid [string cat $comboid "." $rval] + ttk::radiobutton $rbid -variable $boundvar -text $dval -value $rval + grid $rbid -column 0 -row $irow -sticky nswe + incr irow + } + grid $comboid -column $col -row $row -sticky news -padx 5 -pady 3 + incr col + if {$col > 5} { + set col 0 + incr row + } + } +} + +# right container of the first tab +ttk::frame .tabbar.nopts.right +grid .tabbar.nopts.right -column 6 -row 0 -sticky ne + +# Mines and barrels calc +set hcalc_mines 0 +set hcalc_barrels 0 +set hcalc_result 0 +set calcpref ".tabbar.nopts.right.hcalc" +ttk::labelframe $calcpref -text "Mines & Barrels Calculator" +grid $calcpref -column 0 -row 0 -sticky nw -padx 5 -pady 5 +ttk::label "$calcpref.minelbl" -text Mines +ttk::entry "$calcpref.minetxt" -width 3 -textvariable hcalc_mines +grid $calcpref.minelbl -column 0 -row 0 -sticky nw -padx 5 -pady 5 +grid $calcpref.minetxt -column 1 -row 0 -sticky nw -padx 5 -pady 5 +ttk::label "$calcpref.barrlbl" -text Barrels +ttk::entry "$calcpref.barrtxt" -width 3 -textvariable hcalc_barrels +grid $calcpref.barrlbl -column 0 -row 1 -sticky nw -padx 5 -pady 5 +grid $calcpref.barrtxt -column 1 -row 1 -sticky nw -padx 5 -pady 5 +ttk::button "$calcpref.reset" -text Reset -command { + set hcalc_mines 0 + set hcalc_barrels 0 + set hcalc_result 0 +} +grid $calcpref.reset -column 2 -row 0 -sticky nw -padx 5 -pady 5 +ttk::button "$calcpref.decode" -text Decode -command { + # get encoded value + set encval $wsc_data(22) + if {$encval eq ""} {set encval 0} + if {$encval < 8} { # old logic + switch $encval { + 1 { # 8 mines + set hcalc_mines 8 + set hcalc_barrels 0 + } + 2 { # 8 barrels + set hcalc_mines 0 + set hcalc_barrels 8 + } + 5 { # up to 4 mines and up to 4 barrels + set hcalc_mines 4 + set hcalc_barrels 4 + } + default { # no objects + set hcalc_mines 0 + set hcalc_barrels 0 + } + } + } else { # new logic + set xmod [expr {int($encval) % 4}] + switch $xmod { + 0 { # no objects + set hcalc_mines 0 + set hcalc_barrels 0 + } + 1 { # only mines + set hcalc_barrels 0 + set hcalc_mines [decode_hobj_count $encval] + } + 2 { # only barrels + set hcalc_mines 0 + set hcalc_barrels [decode_hobj_count $encval] + } + 3 { # both object types, approximate the count + set objcount [decode_hobj_count $encval] + set half [expr {$objcount >> 1}] + set hcalc_mines $half + set hcalc_barrels [expr {$objcount - $half}] + } + } + } +} +grid $calcpref.decode -column 2 -row 1 -sticky nw -padx 5 -pady 5 +ttk::button "$calcpref.docalc" -text Calculate -command { + set total_objects [expr {int($hcalc_mines) + int($hcalc_barrels)}] + set res 0 + if {$total_objects < 9} { # turn on old logic + if {$total_objects eq 0} { # 0 mines, 0 barrels + set res 0 + } elseif {$hcalc_mines eq 0} { # 0 mines, 8 barrels + set res 2 + } elseif {$hcalc_barrels eq 0} { # 8 mines, 0 barrels + set res 1 + } else { # approx. 4 mines, 4 barrels + set res 5 + } + } else { # use new logic + # find the closest (bigger) total_objects value in wa_ho_dlist + foreach val $wa_ho_dlist { + if {$val >= $total_objects} { + set total_objects $val + break + } + } + # max cap is at 250 objects + if {$val > 250} {set total_objects 250} + # reverse the algorithm + set e [lsearch $wa_ho_dlist $total_objects] + incr e + set m 3 + if {$hcalc_mines eq 0} {set m 2} elseif {$hcalc_barrels eq 0} {set m 1} + set res [expr {$e * 4 + 8 + $m}] + } + # set the result label + set hcalc_result $res + # update the actual HOS value + set wsc_data(22) $res + # perform control decoding + event generate $calcpref.decode + event generate $calcpref.decode +} +grid $calcpref.docalc -column 2 -row 2 -sticky nw -padx 5 -pady 5 +ttk::label "$calcpref.resultlbl" -text "Result:" +grid $calcpref.resultlbl -column 0 -row 2 -sticky nw -padx 5 -pady 5 +ttk::label "$calcpref.result" -textvariable hcalc_result +grid $calcpref.result -column 1 -row 2 -sticky nw -padx 5 -pady 5 + +# Version overrider +set verpref ".tabbar.nopts.right.vers" +ttk::labelframe $verpref -text "Version converter" +ttk::label "$verpref.verlbl" -text "Format version" +ttk::entry "$verpref.vertxt" -width 1 -textvariable wsc_data(version) +grid $verpref.verlbl -column 0 -row 1 -sticky nws -padx 5 -pady 5 +grid $verpref.vertxt -column 1 -row 1 -sticky nw -padx 5 -pady 5 +grid $verpref -column 0 -row 1 -sticky nw -padx 5 -pady 5 +