Files
ArmageDec/armagedec.tcl
T

857 lines
32 KiB
Tcl
Executable File

#!/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.5
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 fpint [expr {int($fpint)}]
set whole [expr {$fpint >> 16}]
set frac [expr {$fpint % 65536}]
return [expr {$whole + ($frac / 65536.)}]
}
proc float2fixed {flt} {
set whole [expr {int($flt)}]
set frac [expr {$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)}]
set poweroffs [expr {$offs + 1}]
set delayoffs [expr {$offs + 2}]
set proboffs [expr {$offs + 3}]
set ammo 0
set power 0
set delay 0
set prob 0
catch {
binary scan [string range $rawschemedata $offs [expr {$offs + 3}]] 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}
set newval ""
binary scan [string range $rawschemedata $offs [expr {$offs + $size - 1}]] $fmt newval
if {$newval ne ""} {set wscval $newval}
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
if [string match *.wagame [string tolower $fname]] { # extract from the replay file
set fp [open $fname r]
fconfigure $fp -translation binary
set replaydata [read $fp]
close $fp
set schemestart [string first "SCHM" $replaydata]
if {$schemestart < 0} {
tk_messageBox -type ok -title "Scheme loading error" -icon error -message "No scheme found in the replay file!"
return
}
# read the version
set offs [expr {$schemestart + 4}]
set scanval [string index $replaydata $offs]
binary scan $scanval cu scmver
# set the target size based on the version
set datasize 221
if {$scmver eq 2} {set datasize 297}
if {$scmver eq 3} {set datasize 407}
set rawschemedata [string range $replaydata $schemestart [expr {$schemestart + $datasize - 1}]]
} else { # read raw scheme data directly
set fp [open $fname r]
fconfigure $fp -translation binary
set rawschemedata [read $fp]
close $fp
}
set hdr ""
set ver ""
binary scan $rawschemedata a4cu 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}
# detect the target size
set datasize 221
if {int($wsc_data(version)) eq 2} {set datasize 297}
if {int($wsc_data(version)) eq 3} {set datasize 407}
# expand/truncate rawschemedata according to the detected size
set diffbytes [expr {$datasize - [string length $rawschemedata]}]
if {$diffbytes > 0} { # expand with nulls
set rawschemedata [string cat $rawschemedata [binary format x$diffbytes]]
} else { # truncate
set rawschemedata [string range $rawschemedata 0 [expr {$datasize - 1}]]
}
# write the version itself
set binval [binary format cu $wsc_data(version)]
set rawschemedata [string replace $rawschemedata 4 4 $binval]
# 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]_write"
set val [$writefunc $val]
} elseif {$valopt eq "fixp"} {
set val [float2fixed $val]
}
set binval [binary format $fmt $val]
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)
# or a .WAgame replay file
proc openwsc {} {
global wscfile appdir
set startdir $appdir
if {$wscfile ne ""} {set startdir [file dirname $wscfile]}
set types {
{{Worms: Armageddon game scheme} {.wsc} BINA}
{{Worms: Armageddon replay} {.WAgame} BINA}
}
return [tk_getOpenFile -filetypes $types -initialdir $startdir -title {Open a Worms scheme or replay 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 <ButtonPress-1>
event generate $calcpref.decode <ButtonRelease-1>
}
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