diff --git a/megamek/data/images/hexes/nuke/hit/col_0_row_0.png b/megamek/data/images/hexes/nuke/hit/col_0_row_0.png
new file mode 100644
index 00000000000..09210170436
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_0_row_0.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_0_row_0_odd.png b/megamek/data/images/hexes/nuke/hit/col_0_row_0_odd.png
new file mode 100644
index 00000000000..1d78aaaca57
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_0_row_0_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_0_row_1.png b/megamek/data/images/hexes/nuke/hit/col_0_row_1.png
new file mode 100644
index 00000000000..e252dc1865f
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_0_row_1.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_0_row_1_odd.png b/megamek/data/images/hexes/nuke/hit/col_0_row_1_odd.png
new file mode 100644
index 00000000000..bb37b0672de
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_0_row_1_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_0_row_2.png b/megamek/data/images/hexes/nuke/hit/col_0_row_2.png
new file mode 100644
index 00000000000..8af905b9a8f
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_0_row_2.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_0_row_2_odd.png b/megamek/data/images/hexes/nuke/hit/col_0_row_2_odd.png
new file mode 100644
index 00000000000..59626752ef9
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_0_row_2_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_0_row_3.png b/megamek/data/images/hexes/nuke/hit/col_0_row_3.png
new file mode 100644
index 00000000000..69bbd55f1be
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_0_row_3.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_0_row_3_odd.png b/megamek/data/images/hexes/nuke/hit/col_0_row_3_odd.png
new file mode 100644
index 00000000000..9974a8b1ed3
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_0_row_3_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_0_row_4.png b/megamek/data/images/hexes/nuke/hit/col_0_row_4.png
new file mode 100644
index 00000000000..127aa851645
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_0_row_4.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_0_row_4_odd.png b/megamek/data/images/hexes/nuke/hit/col_0_row_4_odd.png
new file mode 100644
index 00000000000..6868e887a72
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_0_row_4_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_0_row_5.png b/megamek/data/images/hexes/nuke/hit/col_0_row_5.png
new file mode 100644
index 00000000000..a9f7fe64418
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_0_row_5.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_0_row_5_odd.png b/megamek/data/images/hexes/nuke/hit/col_0_row_5_odd.png
new file mode 100644
index 00000000000..8575ceb8df8
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_0_row_5_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_0_row_6.png b/megamek/data/images/hexes/nuke/hit/col_0_row_6.png
new file mode 100644
index 00000000000..55548267c87
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_0_row_6.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_0_row_6_odd.png b/megamek/data/images/hexes/nuke/hit/col_0_row_6_odd.png
new file mode 100644
index 00000000000..db700a6f889
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_0_row_6_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_0_row_7.png b/megamek/data/images/hexes/nuke/hit/col_0_row_7.png
new file mode 100644
index 00000000000..6a45296e7d5
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_0_row_7.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_0_row_7_odd.png b/megamek/data/images/hexes/nuke/hit/col_0_row_7_odd.png
new file mode 100644
index 00000000000..857551cd2e3
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_0_row_7_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_0_row_8.png b/megamek/data/images/hexes/nuke/hit/col_0_row_8.png
new file mode 100644
index 00000000000..c4fdc496420
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_0_row_8.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_0_row_8_odd.png b/megamek/data/images/hexes/nuke/hit/col_0_row_8_odd.png
new file mode 100644
index 00000000000..625aa8294aa
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_0_row_8_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_1_row_0.png b/megamek/data/images/hexes/nuke/hit/col_1_row_0.png
new file mode 100644
index 00000000000..8d16d356a00
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_1_row_0.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_1_row_0_odd.png b/megamek/data/images/hexes/nuke/hit/col_1_row_0_odd.png
new file mode 100644
index 00000000000..f357422af8c
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_1_row_0_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_1_row_1.png b/megamek/data/images/hexes/nuke/hit/col_1_row_1.png
new file mode 100644
index 00000000000..89dc524127c
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_1_row_1.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_1_row_1_odd.png b/megamek/data/images/hexes/nuke/hit/col_1_row_1_odd.png
new file mode 100644
index 00000000000..13e31dbce1d
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_1_row_1_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_1_row_2.png b/megamek/data/images/hexes/nuke/hit/col_1_row_2.png
new file mode 100644
index 00000000000..aef4c9c03b5
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_1_row_2.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_1_row_2_odd.png b/megamek/data/images/hexes/nuke/hit/col_1_row_2_odd.png
new file mode 100644
index 00000000000..85d2c723a68
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_1_row_2_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_1_row_3.png b/megamek/data/images/hexes/nuke/hit/col_1_row_3.png
new file mode 100644
index 00000000000..93dd02076da
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_1_row_3.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_1_row_3_odd.png b/megamek/data/images/hexes/nuke/hit/col_1_row_3_odd.png
new file mode 100644
index 00000000000..b917e9ec085
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_1_row_3_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_1_row_4.png b/megamek/data/images/hexes/nuke/hit/col_1_row_4.png
new file mode 100644
index 00000000000..28751013823
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_1_row_4.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_1_row_4_odd.png b/megamek/data/images/hexes/nuke/hit/col_1_row_4_odd.png
new file mode 100644
index 00000000000..bfeeaaebbc2
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_1_row_4_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_1_row_5.png b/megamek/data/images/hexes/nuke/hit/col_1_row_5.png
new file mode 100644
index 00000000000..b3a519266e6
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_1_row_5.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_1_row_5_odd.png b/megamek/data/images/hexes/nuke/hit/col_1_row_5_odd.png
new file mode 100644
index 00000000000..6d7700e1de6
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_1_row_5_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_1_row_6.png b/megamek/data/images/hexes/nuke/hit/col_1_row_6.png
new file mode 100644
index 00000000000..b11947de993
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_1_row_6.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_1_row_6_odd.png b/megamek/data/images/hexes/nuke/hit/col_1_row_6_odd.png
new file mode 100644
index 00000000000..cbe8b1548da
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_1_row_6_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_1_row_7.png b/megamek/data/images/hexes/nuke/hit/col_1_row_7.png
new file mode 100644
index 00000000000..5b0f9b554e2
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_1_row_7.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_1_row_7_odd.png b/megamek/data/images/hexes/nuke/hit/col_1_row_7_odd.png
new file mode 100644
index 00000000000..1fbb773755a
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_1_row_7_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_1_row_8.png b/megamek/data/images/hexes/nuke/hit/col_1_row_8.png
new file mode 100644
index 00000000000..0b2c92e0ed1
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_1_row_8.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_1_row_8_odd.png b/megamek/data/images/hexes/nuke/hit/col_1_row_8_odd.png
new file mode 100644
index 00000000000..3f85db43913
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_1_row_8_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_2_row_0.png b/megamek/data/images/hexes/nuke/hit/col_2_row_0.png
new file mode 100644
index 00000000000..135c6a98ae5
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_2_row_0.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_2_row_0_odd.png b/megamek/data/images/hexes/nuke/hit/col_2_row_0_odd.png
new file mode 100644
index 00000000000..1a1a38b23de
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_2_row_0_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_2_row_1.png b/megamek/data/images/hexes/nuke/hit/col_2_row_1.png
new file mode 100644
index 00000000000..98cf4453a52
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_2_row_1.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_2_row_1_odd.png b/megamek/data/images/hexes/nuke/hit/col_2_row_1_odd.png
new file mode 100644
index 00000000000..61148eef047
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_2_row_1_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_2_row_2.png b/megamek/data/images/hexes/nuke/hit/col_2_row_2.png
new file mode 100644
index 00000000000..7a2c70c4de9
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_2_row_2.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_2_row_2_odd.png b/megamek/data/images/hexes/nuke/hit/col_2_row_2_odd.png
new file mode 100644
index 00000000000..b061912bec3
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_2_row_2_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_2_row_3.png b/megamek/data/images/hexes/nuke/hit/col_2_row_3.png
new file mode 100644
index 00000000000..24d06daa0a5
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_2_row_3.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_2_row_3_odd.png b/megamek/data/images/hexes/nuke/hit/col_2_row_3_odd.png
new file mode 100644
index 00000000000..994c627c79b
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_2_row_3_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_2_row_4.png b/megamek/data/images/hexes/nuke/hit/col_2_row_4.png
new file mode 100644
index 00000000000..c4b569721c2
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_2_row_4.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_2_row_4_odd.png b/megamek/data/images/hexes/nuke/hit/col_2_row_4_odd.png
new file mode 100644
index 00000000000..a48e2c81bb3
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_2_row_4_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_2_row_5.png b/megamek/data/images/hexes/nuke/hit/col_2_row_5.png
new file mode 100644
index 00000000000..8a938ee8853
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_2_row_5.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_2_row_5_odd.png b/megamek/data/images/hexes/nuke/hit/col_2_row_5_odd.png
new file mode 100644
index 00000000000..abb80953c92
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_2_row_5_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_2_row_6.png b/megamek/data/images/hexes/nuke/hit/col_2_row_6.png
new file mode 100644
index 00000000000..cb173a8bda3
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_2_row_6.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_2_row_6_odd.png b/megamek/data/images/hexes/nuke/hit/col_2_row_6_odd.png
new file mode 100644
index 00000000000..c648dd543c6
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_2_row_6_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_2_row_7.png b/megamek/data/images/hexes/nuke/hit/col_2_row_7.png
new file mode 100644
index 00000000000..e111bc2a567
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_2_row_7.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_2_row_7_odd.png b/megamek/data/images/hexes/nuke/hit/col_2_row_7_odd.png
new file mode 100644
index 00000000000..a69d546f457
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_2_row_7_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_2_row_8.png b/megamek/data/images/hexes/nuke/hit/col_2_row_8.png
new file mode 100644
index 00000000000..1fc13482795
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_2_row_8.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_2_row_8_odd.png b/megamek/data/images/hexes/nuke/hit/col_2_row_8_odd.png
new file mode 100644
index 00000000000..9aad676f8c5
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_2_row_8_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_3_row_0.png b/megamek/data/images/hexes/nuke/hit/col_3_row_0.png
new file mode 100644
index 00000000000..46d6c370c6c
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_3_row_0.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_3_row_0_odd.png b/megamek/data/images/hexes/nuke/hit/col_3_row_0_odd.png
new file mode 100644
index 00000000000..f83365995e4
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_3_row_0_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_3_row_1.png b/megamek/data/images/hexes/nuke/hit/col_3_row_1.png
new file mode 100644
index 00000000000..8d2bb2ff1b2
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_3_row_1.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_3_row_1_odd.png b/megamek/data/images/hexes/nuke/hit/col_3_row_1_odd.png
new file mode 100644
index 00000000000..c90bac0c60f
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_3_row_1_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_3_row_2.png b/megamek/data/images/hexes/nuke/hit/col_3_row_2.png
new file mode 100644
index 00000000000..3c961fdb8bf
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_3_row_2.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_3_row_2_odd.png b/megamek/data/images/hexes/nuke/hit/col_3_row_2_odd.png
new file mode 100644
index 00000000000..910d26bf766
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_3_row_2_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_3_row_3.png b/megamek/data/images/hexes/nuke/hit/col_3_row_3.png
new file mode 100644
index 00000000000..a824a39163b
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_3_row_3.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_3_row_3_odd.png b/megamek/data/images/hexes/nuke/hit/col_3_row_3_odd.png
new file mode 100644
index 00000000000..66993654d70
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_3_row_3_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_3_row_4.png b/megamek/data/images/hexes/nuke/hit/col_3_row_4.png
new file mode 100644
index 00000000000..8b458f6b40d
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_3_row_4.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_3_row_4_odd.png b/megamek/data/images/hexes/nuke/hit/col_3_row_4_odd.png
new file mode 100644
index 00000000000..bd68941d4ec
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_3_row_4_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_3_row_5.png b/megamek/data/images/hexes/nuke/hit/col_3_row_5.png
new file mode 100644
index 00000000000..748ac269960
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_3_row_5.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_3_row_5_odd.png b/megamek/data/images/hexes/nuke/hit/col_3_row_5_odd.png
new file mode 100644
index 00000000000..cdf48ed94ca
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_3_row_5_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_3_row_6.png b/megamek/data/images/hexes/nuke/hit/col_3_row_6.png
new file mode 100644
index 00000000000..15b1f7e1701
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_3_row_6.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_3_row_6_odd.png b/megamek/data/images/hexes/nuke/hit/col_3_row_6_odd.png
new file mode 100644
index 00000000000..23396b6d064
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_3_row_6_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_3_row_7.png b/megamek/data/images/hexes/nuke/hit/col_3_row_7.png
new file mode 100644
index 00000000000..c7d6606b487
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_3_row_7.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_3_row_7_odd.png b/megamek/data/images/hexes/nuke/hit/col_3_row_7_odd.png
new file mode 100644
index 00000000000..b49ded226c2
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_3_row_7_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_3_row_8.png b/megamek/data/images/hexes/nuke/hit/col_3_row_8.png
new file mode 100644
index 00000000000..0329a4a2117
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_3_row_8.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_3_row_8_odd.png b/megamek/data/images/hexes/nuke/hit/col_3_row_8_odd.png
new file mode 100644
index 00000000000..a1817a81452
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_3_row_8_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_4_row_0.png b/megamek/data/images/hexes/nuke/hit/col_4_row_0.png
new file mode 100644
index 00000000000..ccc58b85ec4
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_4_row_0.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_4_row_0_odd.png b/megamek/data/images/hexes/nuke/hit/col_4_row_0_odd.png
new file mode 100644
index 00000000000..17e4f071888
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_4_row_0_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_4_row_1.png b/megamek/data/images/hexes/nuke/hit/col_4_row_1.png
new file mode 100644
index 00000000000..369ef796cdd
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_4_row_1.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_4_row_1_odd.png b/megamek/data/images/hexes/nuke/hit/col_4_row_1_odd.png
new file mode 100644
index 00000000000..2afc336bcea
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_4_row_1_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_4_row_2.png b/megamek/data/images/hexes/nuke/hit/col_4_row_2.png
new file mode 100644
index 00000000000..a9714990670
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_4_row_2.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_4_row_2_odd.png b/megamek/data/images/hexes/nuke/hit/col_4_row_2_odd.png
new file mode 100644
index 00000000000..82eb5231d64
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_4_row_2_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_4_row_3.png b/megamek/data/images/hexes/nuke/hit/col_4_row_3.png
new file mode 100644
index 00000000000..b9e7effb8ae
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_4_row_3.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_4_row_3_odd.png b/megamek/data/images/hexes/nuke/hit/col_4_row_3_odd.png
new file mode 100644
index 00000000000..9b95ab7ff05
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_4_row_3_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_4_row_4.png b/megamek/data/images/hexes/nuke/hit/col_4_row_4.png
new file mode 100644
index 00000000000..4363b7f8fef
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_4_row_4.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_4_row_4_odd.png b/megamek/data/images/hexes/nuke/hit/col_4_row_4_odd.png
new file mode 100644
index 00000000000..93648f15027
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_4_row_4_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_4_row_5.png b/megamek/data/images/hexes/nuke/hit/col_4_row_5.png
new file mode 100644
index 00000000000..c67d29eade9
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_4_row_5.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_4_row_5_odd.png b/megamek/data/images/hexes/nuke/hit/col_4_row_5_odd.png
new file mode 100644
index 00000000000..c95460035a5
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_4_row_5_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_4_row_6.png b/megamek/data/images/hexes/nuke/hit/col_4_row_6.png
new file mode 100644
index 00000000000..5831e6c29ca
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_4_row_6.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_4_row_6_odd.png b/megamek/data/images/hexes/nuke/hit/col_4_row_6_odd.png
new file mode 100644
index 00000000000..4d4407afead
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_4_row_6_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_4_row_7.png b/megamek/data/images/hexes/nuke/hit/col_4_row_7.png
new file mode 100644
index 00000000000..b67c81aad0e
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_4_row_7.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_4_row_7_odd.png b/megamek/data/images/hexes/nuke/hit/col_4_row_7_odd.png
new file mode 100644
index 00000000000..4fc4e6e1292
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_4_row_7_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_4_row_8.png b/megamek/data/images/hexes/nuke/hit/col_4_row_8.png
new file mode 100644
index 00000000000..252fb798cf3
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_4_row_8.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_4_row_8_odd.png b/megamek/data/images/hexes/nuke/hit/col_4_row_8_odd.png
new file mode 100644
index 00000000000..22bb5401266
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_4_row_8_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_5_row_0.png b/megamek/data/images/hexes/nuke/hit/col_5_row_0.png
new file mode 100644
index 00000000000..52d4c8c1cd5
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_5_row_0.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_5_row_0_odd.png b/megamek/data/images/hexes/nuke/hit/col_5_row_0_odd.png
new file mode 100644
index 00000000000..9c05f4a9071
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_5_row_0_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_5_row_1.png b/megamek/data/images/hexes/nuke/hit/col_5_row_1.png
new file mode 100644
index 00000000000..378cb70d4e6
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_5_row_1.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_5_row_1_odd.png b/megamek/data/images/hexes/nuke/hit/col_5_row_1_odd.png
new file mode 100644
index 00000000000..94e1eb1d523
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_5_row_1_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_5_row_2.png b/megamek/data/images/hexes/nuke/hit/col_5_row_2.png
new file mode 100644
index 00000000000..42800e30149
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_5_row_2.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_5_row_2_odd.png b/megamek/data/images/hexes/nuke/hit/col_5_row_2_odd.png
new file mode 100644
index 00000000000..bb47791e6df
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_5_row_2_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_5_row_3.png b/megamek/data/images/hexes/nuke/hit/col_5_row_3.png
new file mode 100644
index 00000000000..ee9995de060
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_5_row_3.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_5_row_3_odd.png b/megamek/data/images/hexes/nuke/hit/col_5_row_3_odd.png
new file mode 100644
index 00000000000..f4857329f45
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_5_row_3_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_5_row_4.png b/megamek/data/images/hexes/nuke/hit/col_5_row_4.png
new file mode 100644
index 00000000000..18be4041e29
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_5_row_4.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_5_row_4_odd.png b/megamek/data/images/hexes/nuke/hit/col_5_row_4_odd.png
new file mode 100644
index 00000000000..4604432a107
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_5_row_4_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_5_row_5.png b/megamek/data/images/hexes/nuke/hit/col_5_row_5.png
new file mode 100644
index 00000000000..455fab89dc9
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_5_row_5.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_5_row_5_odd.png b/megamek/data/images/hexes/nuke/hit/col_5_row_5_odd.png
new file mode 100644
index 00000000000..2afc0bbb82d
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_5_row_5_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_5_row_6.png b/megamek/data/images/hexes/nuke/hit/col_5_row_6.png
new file mode 100644
index 00000000000..b6a182dc4ed
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_5_row_6.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_5_row_6_odd.png b/megamek/data/images/hexes/nuke/hit/col_5_row_6_odd.png
new file mode 100644
index 00000000000..df854c56ed9
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_5_row_6_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_5_row_7.png b/megamek/data/images/hexes/nuke/hit/col_5_row_7.png
new file mode 100644
index 00000000000..6f4b53411d7
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_5_row_7.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_5_row_7_odd.png b/megamek/data/images/hexes/nuke/hit/col_5_row_7_odd.png
new file mode 100644
index 00000000000..8b275e4e41b
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_5_row_7_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_5_row_8.png b/megamek/data/images/hexes/nuke/hit/col_5_row_8.png
new file mode 100644
index 00000000000..66a652f014e
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_5_row_8.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_5_row_8_odd.png b/megamek/data/images/hexes/nuke/hit/col_5_row_8_odd.png
new file mode 100644
index 00000000000..8caa2006a60
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_5_row_8_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_6_row_0.png b/megamek/data/images/hexes/nuke/hit/col_6_row_0.png
new file mode 100644
index 00000000000..8c25d8b70e8
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_6_row_0.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_6_row_0_odd.png b/megamek/data/images/hexes/nuke/hit/col_6_row_0_odd.png
new file mode 100644
index 00000000000..b508fd6a56d
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_6_row_0_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_6_row_1.png b/megamek/data/images/hexes/nuke/hit/col_6_row_1.png
new file mode 100644
index 00000000000..7d17a39111e
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_6_row_1.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_6_row_1_odd.png b/megamek/data/images/hexes/nuke/hit/col_6_row_1_odd.png
new file mode 100644
index 00000000000..9058195a820
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_6_row_1_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_6_row_2.png b/megamek/data/images/hexes/nuke/hit/col_6_row_2.png
new file mode 100644
index 00000000000..e75eb79d31d
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_6_row_2.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_6_row_2_odd.png b/megamek/data/images/hexes/nuke/hit/col_6_row_2_odd.png
new file mode 100644
index 00000000000..ed32d127c6a
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_6_row_2_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_6_row_3.png b/megamek/data/images/hexes/nuke/hit/col_6_row_3.png
new file mode 100644
index 00000000000..90f28991521
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_6_row_3.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_6_row_3_odd.png b/megamek/data/images/hexes/nuke/hit/col_6_row_3_odd.png
new file mode 100644
index 00000000000..3de4728e04a
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_6_row_3_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_6_row_4.png b/megamek/data/images/hexes/nuke/hit/col_6_row_4.png
new file mode 100644
index 00000000000..05f07d9a536
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_6_row_4.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_6_row_4_odd.png b/megamek/data/images/hexes/nuke/hit/col_6_row_4_odd.png
new file mode 100644
index 00000000000..20988ba57c1
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_6_row_4_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_6_row_5.png b/megamek/data/images/hexes/nuke/hit/col_6_row_5.png
new file mode 100644
index 00000000000..99a816fd41e
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_6_row_5.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_6_row_5_odd.png b/megamek/data/images/hexes/nuke/hit/col_6_row_5_odd.png
new file mode 100644
index 00000000000..cbf908c00c8
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_6_row_5_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_6_row_6.png b/megamek/data/images/hexes/nuke/hit/col_6_row_6.png
new file mode 100644
index 00000000000..50747d611bd
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_6_row_6.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_6_row_6_odd.png b/megamek/data/images/hexes/nuke/hit/col_6_row_6_odd.png
new file mode 100644
index 00000000000..5438d10a1d5
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_6_row_6_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_6_row_7.png b/megamek/data/images/hexes/nuke/hit/col_6_row_7.png
new file mode 100644
index 00000000000..6c0ccc5d290
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_6_row_7.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_6_row_7_odd.png b/megamek/data/images/hexes/nuke/hit/col_6_row_7_odd.png
new file mode 100644
index 00000000000..c08709f1d94
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_6_row_7_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_6_row_8.png b/megamek/data/images/hexes/nuke/hit/col_6_row_8.png
new file mode 100644
index 00000000000..3fcd3c348bd
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_6_row_8.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_6_row_8_odd.png b/megamek/data/images/hexes/nuke/hit/col_6_row_8_odd.png
new file mode 100644
index 00000000000..a7c4a899913
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_6_row_8_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_7_row_0.png b/megamek/data/images/hexes/nuke/hit/col_7_row_0.png
new file mode 100644
index 00000000000..7a295b4e4e9
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_7_row_0.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_7_row_0_odd.png b/megamek/data/images/hexes/nuke/hit/col_7_row_0_odd.png
new file mode 100644
index 00000000000..01d9bf18156
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_7_row_0_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_7_row_1.png b/megamek/data/images/hexes/nuke/hit/col_7_row_1.png
new file mode 100644
index 00000000000..978ce614de6
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_7_row_1.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_7_row_1_odd.png b/megamek/data/images/hexes/nuke/hit/col_7_row_1_odd.png
new file mode 100644
index 00000000000..8e2c126ac9e
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_7_row_1_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_7_row_2.png b/megamek/data/images/hexes/nuke/hit/col_7_row_2.png
new file mode 100644
index 00000000000..18be79d6038
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_7_row_2.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_7_row_2_odd.png b/megamek/data/images/hexes/nuke/hit/col_7_row_2_odd.png
new file mode 100644
index 00000000000..530760fbcf4
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_7_row_2_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_7_row_3.png b/megamek/data/images/hexes/nuke/hit/col_7_row_3.png
new file mode 100644
index 00000000000..c7b66216a21
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_7_row_3.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_7_row_3_odd.png b/megamek/data/images/hexes/nuke/hit/col_7_row_3_odd.png
new file mode 100644
index 00000000000..b471e0e0c7f
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_7_row_3_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_7_row_4.png b/megamek/data/images/hexes/nuke/hit/col_7_row_4.png
new file mode 100644
index 00000000000..8d7ced635fb
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_7_row_4.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_7_row_4_odd.png b/megamek/data/images/hexes/nuke/hit/col_7_row_4_odd.png
new file mode 100644
index 00000000000..8bbb1d34d60
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_7_row_4_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_7_row_5.png b/megamek/data/images/hexes/nuke/hit/col_7_row_5.png
new file mode 100644
index 00000000000..2e88da434a5
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_7_row_5.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_7_row_5_odd.png b/megamek/data/images/hexes/nuke/hit/col_7_row_5_odd.png
new file mode 100644
index 00000000000..c6535e72a5a
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_7_row_5_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_7_row_6.png b/megamek/data/images/hexes/nuke/hit/col_7_row_6.png
new file mode 100644
index 00000000000..7d7400face6
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_7_row_6.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_7_row_6_odd.png b/megamek/data/images/hexes/nuke/hit/col_7_row_6_odd.png
new file mode 100644
index 00000000000..86779360bc3
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_7_row_6_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_7_row_7.png b/megamek/data/images/hexes/nuke/hit/col_7_row_7.png
new file mode 100644
index 00000000000..32a9b99b4e1
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_7_row_7.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_7_row_7_odd.png b/megamek/data/images/hexes/nuke/hit/col_7_row_7_odd.png
new file mode 100644
index 00000000000..b5887b5d1f3
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_7_row_7_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_7_row_8.png b/megamek/data/images/hexes/nuke/hit/col_7_row_8.png
new file mode 100644
index 00000000000..94d6ead2b75
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_7_row_8.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_7_row_8_odd.png b/megamek/data/images/hexes/nuke/hit/col_7_row_8_odd.png
new file mode 100644
index 00000000000..3d82f9141bc
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_7_row_8_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_8_row_0.png b/megamek/data/images/hexes/nuke/hit/col_8_row_0.png
new file mode 100644
index 00000000000..cfd1fc93b2d
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_8_row_0.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_8_row_0_odd.png b/megamek/data/images/hexes/nuke/hit/col_8_row_0_odd.png
new file mode 100644
index 00000000000..90373d4cc3e
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_8_row_0_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_8_row_1.png b/megamek/data/images/hexes/nuke/hit/col_8_row_1.png
new file mode 100644
index 00000000000..611c27f454e
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_8_row_1.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_8_row_1_odd.png b/megamek/data/images/hexes/nuke/hit/col_8_row_1_odd.png
new file mode 100644
index 00000000000..c092502cab5
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_8_row_1_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_8_row_2.png b/megamek/data/images/hexes/nuke/hit/col_8_row_2.png
new file mode 100644
index 00000000000..241381715f5
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_8_row_2.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_8_row_2_odd.png b/megamek/data/images/hexes/nuke/hit/col_8_row_2_odd.png
new file mode 100644
index 00000000000..0cd2545c0e8
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_8_row_2_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_8_row_3.png b/megamek/data/images/hexes/nuke/hit/col_8_row_3.png
new file mode 100644
index 00000000000..4ac65b3f7dd
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_8_row_3.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_8_row_3_odd.png b/megamek/data/images/hexes/nuke/hit/col_8_row_3_odd.png
new file mode 100644
index 00000000000..bc9f00cea65
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_8_row_3_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_8_row_4.png b/megamek/data/images/hexes/nuke/hit/col_8_row_4.png
new file mode 100644
index 00000000000..039968f942a
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_8_row_4.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_8_row_4_odd.png b/megamek/data/images/hexes/nuke/hit/col_8_row_4_odd.png
new file mode 100644
index 00000000000..bc151a9a912
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_8_row_4_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_8_row_5.png b/megamek/data/images/hexes/nuke/hit/col_8_row_5.png
new file mode 100644
index 00000000000..32556f17c93
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_8_row_5.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_8_row_5_odd.png b/megamek/data/images/hexes/nuke/hit/col_8_row_5_odd.png
new file mode 100644
index 00000000000..14c46ef3dd1
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_8_row_5_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_8_row_6.png b/megamek/data/images/hexes/nuke/hit/col_8_row_6.png
new file mode 100644
index 00000000000..4ead2acb642
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_8_row_6.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_8_row_6_odd.png b/megamek/data/images/hexes/nuke/hit/col_8_row_6_odd.png
new file mode 100644
index 00000000000..b66adb25025
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_8_row_6_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_8_row_7.png b/megamek/data/images/hexes/nuke/hit/col_8_row_7.png
new file mode 100644
index 00000000000..db9e22d5015
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_8_row_7.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_8_row_7_odd.png b/megamek/data/images/hexes/nuke/hit/col_8_row_7_odd.png
new file mode 100644
index 00000000000..af85ea03ce2
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_8_row_7_odd.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_8_row_8.png b/megamek/data/images/hexes/nuke/hit/col_8_row_8.png
new file mode 100644
index 00000000000..cad7332abb8
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_8_row_8.png differ
diff --git a/megamek/data/images/hexes/nuke/hit/col_8_row_8_odd.png b/megamek/data/images/hexes/nuke/hit/col_8_row_8_odd.png
new file mode 100644
index 00000000000..68774355c55
Binary files /dev/null and b/megamek/data/images/hexes/nuke/hit/col_8_row_8_odd.png differ
diff --git a/megamek/data/images/hexes/nukeinc.gif b/megamek/data/images/hexes/nukeinc.gif
new file mode 100644
index 00000000000..c5f4c4b6b2d
Binary files /dev/null and b/megamek/data/images/hexes/nukeinc.gif differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_0.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_0.png
new file mode 100644
index 00000000000..1fb45de2538
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_0.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_0_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_0_odd.png
new file mode 100644
index 00000000000..bb7d6a53f02
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_0_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_1.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_1.png
new file mode 100644
index 00000000000..8a518277ae2
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_1.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_1_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_1_odd.png
new file mode 100644
index 00000000000..8a518277ae2
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_1_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_2.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_2.png
new file mode 100644
index 00000000000..e1d87c0801a
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_2.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_2_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_2_odd.png
new file mode 100644
index 00000000000..52978756a9e
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_2_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_3.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_3.png
new file mode 100644
index 00000000000..e1c1a297e44
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_3.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_3_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_3_odd.png
new file mode 100644
index 00000000000..76aff994f36
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_3_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_4.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_4.png
new file mode 100644
index 00000000000..aa6ba328f43
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_4.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_4_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_4_odd.png
new file mode 100644
index 00000000000..c8d64e5957a
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_4_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_5.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_5.png
new file mode 100644
index 00000000000..725bba6ed0a
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_5.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_5_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_5_odd.png
new file mode 100644
index 00000000000..4ff8cc77466
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_5_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_6.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_6.png
new file mode 100644
index 00000000000..4ab163de639
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_6.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_6_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_6_odd.png
new file mode 100644
index 00000000000..d007652af0e
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_6_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_7.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_7.png
new file mode 100644
index 00000000000..ea1276ccf4e
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_7.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_7_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_7_odd.png
new file mode 100644
index 00000000000..bcf21151665
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_7_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_8.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_8.png
new file mode 100644
index 00000000000..eebe32c11e2
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_8.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_8_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_8_odd.png
new file mode 100644
index 00000000000..8a518277ae2
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_0_row_8_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_0.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_0.png
new file mode 100644
index 00000000000..aaf1cc1ba7f
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_0.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_0_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_0_odd.png
new file mode 100644
index 00000000000..36f7c8a0b54
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_0_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_1.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_1.png
new file mode 100644
index 00000000000..cd6e90999d0
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_1.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_1_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_1_odd.png
new file mode 100644
index 00000000000..ff75fe7ede9
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_1_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_2.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_2.png
new file mode 100644
index 00000000000..f2b3abfc558
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_2.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_2_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_2_odd.png
new file mode 100644
index 00000000000..17bd111ab80
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_2_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_3.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_3.png
new file mode 100644
index 00000000000..cdb632195ac
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_3.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_3_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_3_odd.png
new file mode 100644
index 00000000000..417dad71bd4
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_3_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_4.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_4.png
new file mode 100644
index 00000000000..dd5e5154c88
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_4.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_4_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_4_odd.png
new file mode 100644
index 00000000000..869f2123735
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_4_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_5.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_5.png
new file mode 100644
index 00000000000..ed2a35e17c9
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_5.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_5_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_5_odd.png
new file mode 100644
index 00000000000..16b139215a2
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_5_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_6.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_6.png
new file mode 100644
index 00000000000..abd34e4a574
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_6.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_6_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_6_odd.png
new file mode 100644
index 00000000000..633a694ae50
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_6_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_7.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_7.png
new file mode 100644
index 00000000000..7fcdfe54579
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_7.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_7_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_7_odd.png
new file mode 100644
index 00000000000..e4e4eb5c3d9
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_7_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_8.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_8.png
new file mode 100644
index 00000000000..8a518277ae2
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_8.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_8_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_8_odd.png
new file mode 100644
index 00000000000..ccee2a42ab0
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_1_row_8_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_0.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_0.png
new file mode 100644
index 00000000000..7012fee2f96
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_0.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_0_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_0_odd.png
new file mode 100644
index 00000000000..6b9172d14c7
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_0_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_1.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_1.png
new file mode 100644
index 00000000000..1626fef302e
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_1.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_1_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_1_odd.png
new file mode 100644
index 00000000000..c8ba76518bf
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_1_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_2.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_2.png
new file mode 100644
index 00000000000..7a8b0428cfd
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_2.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_2_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_2_odd.png
new file mode 100644
index 00000000000..deb3b582c20
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_2_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_3.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_3.png
new file mode 100644
index 00000000000..78c1f3aca40
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_3.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_3_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_3_odd.png
new file mode 100644
index 00000000000..400d541c8a6
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_3_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_4.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_4.png
new file mode 100644
index 00000000000..998a28eccc5
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_4.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_4_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_4_odd.png
new file mode 100644
index 00000000000..f5491daf5dc
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_4_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_5.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_5.png
new file mode 100644
index 00000000000..e6d0078b0be
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_5.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_5_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_5_odd.png
new file mode 100644
index 00000000000..b18af7c801a
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_5_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_6.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_6.png
new file mode 100644
index 00000000000..02dc7078f2d
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_6.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_6_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_6_odd.png
new file mode 100644
index 00000000000..bbbaaa5c93d
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_6_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_7.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_7.png
new file mode 100644
index 00000000000..fbfc9d96184
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_7.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_7_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_7_odd.png
new file mode 100644
index 00000000000..461b4d0cc74
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_7_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_8.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_8.png
new file mode 100644
index 00000000000..2d63444ad6c
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_8.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_8_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_8_odd.png
new file mode 100644
index 00000000000..6b052705811
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_2_row_8_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_0.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_0.png
new file mode 100644
index 00000000000..479a6047038
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_0.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_0_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_0_odd.png
new file mode 100644
index 00000000000..0d5ca6340ef
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_0_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_1.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_1.png
new file mode 100644
index 00000000000..a426b965b02
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_1.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_1_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_1_odd.png
new file mode 100644
index 00000000000..2a9f656ed18
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_1_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_2.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_2.png
new file mode 100644
index 00000000000..e9c1bb7b4ab
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_2.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_2_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_2_odd.png
new file mode 100644
index 00000000000..f4416c417dc
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_2_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_3.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_3.png
new file mode 100644
index 00000000000..6ba177f1a77
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_3.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_3_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_3_odd.png
new file mode 100644
index 00000000000..88eaa12b8e8
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_3_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_4.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_4.png
new file mode 100644
index 00000000000..f64945d4d10
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_4.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_4_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_4_odd.png
new file mode 100644
index 00000000000..06386d34fbb
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_4_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_5.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_5.png
new file mode 100644
index 00000000000..8b840523daf
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_5.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_5_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_5_odd.png
new file mode 100644
index 00000000000..f49455f5132
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_5_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_6.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_6.png
new file mode 100644
index 00000000000..bf32fa8ba90
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_6.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_6_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_6_odd.png
new file mode 100644
index 00000000000..398effbfb75
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_6_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_7.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_7.png
new file mode 100644
index 00000000000..bc76833ea2c
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_7.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_7_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_7_odd.png
new file mode 100644
index 00000000000..ae90a76058a
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_7_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_8.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_8.png
new file mode 100644
index 00000000000..4b0fcffce74
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_8.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_8_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_8_odd.png
new file mode 100644
index 00000000000..2a4badddeaf
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_3_row_8_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_0.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_0.png
new file mode 100644
index 00000000000..6dd5ae82ee2
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_0.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_0_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_0_odd.png
new file mode 100644
index 00000000000..4c76f54d8c4
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_0_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_1.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_1.png
new file mode 100644
index 00000000000..05caef14d10
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_1.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_1_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_1_odd.png
new file mode 100644
index 00000000000..87497a2ee01
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_1_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_2.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_2.png
new file mode 100644
index 00000000000..edf0a10d777
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_2.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_2_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_2_odd.png
new file mode 100644
index 00000000000..73a892515c4
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_2_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_3.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_3.png
new file mode 100644
index 00000000000..2606942a211
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_3.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_3_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_3_odd.png
new file mode 100644
index 00000000000..ef9af863339
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_3_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_4.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_4.png
new file mode 100644
index 00000000000..6c42dc083c5
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_4.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_4_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_4_odd.png
new file mode 100644
index 00000000000..067d57f4a6e
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_4_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_5.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_5.png
new file mode 100644
index 00000000000..f033b1af66d
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_5.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_5_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_5_odd.png
new file mode 100644
index 00000000000..4f711d5cfb4
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_5_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_6.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_6.png
new file mode 100644
index 00000000000..62cc3d1961a
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_6.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_6_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_6_odd.png
new file mode 100644
index 00000000000..7d4ad68626b
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_6_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_7.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_7.png
new file mode 100644
index 00000000000..7bb2b6802eb
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_7.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_7_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_7_odd.png
new file mode 100644
index 00000000000..5c390504c5a
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_7_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_8.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_8.png
new file mode 100644
index 00000000000..76376325f7a
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_8.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_8_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_8_odd.png
new file mode 100644
index 00000000000..8a518277ae2
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_4_row_8_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_0.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_0.png
new file mode 100644
index 00000000000..79ae19f5774
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_0.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_0_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_0_odd.png
new file mode 100644
index 00000000000..f4327786c2b
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_0_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_1.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_1.png
new file mode 100644
index 00000000000..9d512ac18bb
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_1.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_1_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_1_odd.png
new file mode 100644
index 00000000000..5acc5f21a5b
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_1_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_2.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_2.png
new file mode 100644
index 00000000000..c336a8745c7
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_2.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_2_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_2_odd.png
new file mode 100644
index 00000000000..9c7ff7b5139
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_2_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_3.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_3.png
new file mode 100644
index 00000000000..5f630d0d301
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_3.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_3_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_3_odd.png
new file mode 100644
index 00000000000..3b3be0df749
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_3_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_4.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_4.png
new file mode 100644
index 00000000000..e2e39ea82ac
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_4.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_4_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_4_odd.png
new file mode 100644
index 00000000000..9c2fc878fe8
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_4_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_5.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_5.png
new file mode 100644
index 00000000000..c9af056e2b5
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_5.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_5_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_5_odd.png
new file mode 100644
index 00000000000..7292b863fb5
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_5_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_6.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_6.png
new file mode 100644
index 00000000000..5dad33a5ade
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_6.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_6_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_6_odd.png
new file mode 100644
index 00000000000..623fb932d7a
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_6_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_7.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_7.png
new file mode 100644
index 00000000000..9411e6d4ac7
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_7.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_7_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_7_odd.png
new file mode 100644
index 00000000000..c0d03722a1f
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_7_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_8.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_8.png
new file mode 100644
index 00000000000..8a518277ae2
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_8.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_8_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_8_odd.png
new file mode 100644
index 00000000000..2402f8d0110
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_5_row_8_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_0.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_0.png
new file mode 100644
index 00000000000..8326dd791a4
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_0.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_0_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_0_odd.png
new file mode 100644
index 00000000000..68cbf738a3e
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_0_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_1.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_1.png
new file mode 100644
index 00000000000..b8b5d5770bc
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_1.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_1_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_1_odd.png
new file mode 100644
index 00000000000..3ca2e5fc166
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_1_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_2.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_2.png
new file mode 100644
index 00000000000..d47b9d8d153
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_2.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_2_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_2_odd.png
new file mode 100644
index 00000000000..6ee57f24b93
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_2_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_3.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_3.png
new file mode 100644
index 00000000000..bc39b0e312b
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_3.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_3_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_3_odd.png
new file mode 100644
index 00000000000..dc34dc845ee
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_3_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_4.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_4.png
new file mode 100644
index 00000000000..eca8292c221
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_4.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_4_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_4_odd.png
new file mode 100644
index 00000000000..714a5d9af76
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_4_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_5.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_5.png
new file mode 100644
index 00000000000..e80dadf34d3
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_5.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_5_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_5_odd.png
new file mode 100644
index 00000000000..b975c2e6d5a
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_5_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_6.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_6.png
new file mode 100644
index 00000000000..eddd2df2eb4
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_6.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_6_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_6_odd.png
new file mode 100644
index 00000000000..b6ff5460bba
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_6_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_7.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_7.png
new file mode 100644
index 00000000000..fcf7094404e
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_7.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_7_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_7_odd.png
new file mode 100644
index 00000000000..e4675840408
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_7_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_8.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_8.png
new file mode 100644
index 00000000000..efe55bdd7fb
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_8.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_8_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_8_odd.png
new file mode 100644
index 00000000000..d23dee41198
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_6_row_8_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_0.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_0.png
new file mode 100644
index 00000000000..34cf97bf328
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_0.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_0_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_0_odd.png
new file mode 100644
index 00000000000..7e6e2deb2cf
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_0_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_1.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_1.png
new file mode 100644
index 00000000000..63da3c501cb
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_1.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_1_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_1_odd.png
new file mode 100644
index 00000000000..5d6be2cf2e5
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_1_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_2.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_2.png
new file mode 100644
index 00000000000..23671afbc73
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_2.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_2_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_2_odd.png
new file mode 100644
index 00000000000..db32ea73406
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_2_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_3.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_3.png
new file mode 100644
index 00000000000..f1fcfe0dd55
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_3.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_3_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_3_odd.png
new file mode 100644
index 00000000000..cb9dfd66cc0
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_3_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_4.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_4.png
new file mode 100644
index 00000000000..9e29c633891
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_4.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_4_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_4_odd.png
new file mode 100644
index 00000000000..a0f8d7ea1b1
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_4_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_5.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_5.png
new file mode 100644
index 00000000000..3dc15077d2a
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_5.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_5_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_5_odd.png
new file mode 100644
index 00000000000..f4a1c94eb8b
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_5_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_6.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_6.png
new file mode 100644
index 00000000000..7f0bb9bddfa
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_6.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_6_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_6_odd.png
new file mode 100644
index 00000000000..d854af94d89
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_6_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_7.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_7.png
new file mode 100644
index 00000000000..5b878dd8111
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_7.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_7_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_7_odd.png
new file mode 100644
index 00000000000..080884c4322
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_7_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_8.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_8.png
new file mode 100644
index 00000000000..0a53fee7a95
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_8.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_8_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_8_odd.png
new file mode 100644
index 00000000000..fb8eb069c13
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_7_row_8_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_0.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_0.png
new file mode 100644
index 00000000000..8a518277ae2
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_0.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_0_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_0_odd.png
new file mode 100644
index 00000000000..00d484915fe
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_0_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_1.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_1.png
new file mode 100644
index 00000000000..2311159fdd8
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_1.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_1_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_1_odd.png
new file mode 100644
index 00000000000..cceeec693ec
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_1_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_2.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_2.png
new file mode 100644
index 00000000000..c90b299c3c4
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_2.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_2_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_2_odd.png
new file mode 100644
index 00000000000..2f9ac0f1c5e
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_2_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_3.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_3.png
new file mode 100644
index 00000000000..2f29540d503
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_3.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_3_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_3_odd.png
new file mode 100644
index 00000000000..abe5bc246ab
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_3_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_4.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_4.png
new file mode 100644
index 00000000000..a1c3075b4a2
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_4.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_4_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_4_odd.png
new file mode 100644
index 00000000000..220a820dd25
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_4_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_5.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_5.png
new file mode 100644
index 00000000000..c2088dc0425
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_5.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_5_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_5_odd.png
new file mode 100644
index 00000000000..c26d838cfde
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_5_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_6.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_6.png
new file mode 100644
index 00000000000..a340a91d9e2
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_6.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_6_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_6_odd.png
new file mode 100644
index 00000000000..5829c97ee65
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_6_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_7.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_7.png
new file mode 100644
index 00000000000..6024558d5cf
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_7.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_7_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_7_odd.png
new file mode 100644
index 00000000000..d81a206471f
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_7_odd.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_8.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_8.png
new file mode 100644
index 00000000000..f9dbf685712
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_8.png differ
diff --git a/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_8_odd.png b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_8_odd.png
new file mode 100644
index 00000000000..8a518277ae2
Binary files /dev/null and b/megamek/data/images/hexes/orbital_bombardment/hit/col_8_row_8_odd.png differ
diff --git a/megamek/docs/history.txt b/megamek/docs/history.txt
index b598c773ccb..0360994c551 100644
--- a/megamek/docs/history.txt
+++ b/megamek/docs/history.txt
@@ -1,6 +1,14 @@
MEGAMEK VERSION HISTORY:
----------------
0.50.02-SNAPSHOT
++ PR #6183: New GM Commands, princess commands on map menu, graphics for some explosions
++ PR #6186: Allow pausing a Princess-only game
++ PR #6190: Allow printing unit list from MM
++ Fix #433: Add an advanced search for boards to the lobby
++ Fix #5086: Updated SRMAXHandler to ignore specialty armors
++ PR #6209: Implement RISC laser pulse module
++ Fix #6211: Added null check for lastUnitMember
++ PR #6203: Dev Tool to Find duplicate boards
0.50.01 (2024-11-10 1800 UTC)
+ PR #5962, #5964, #5966, #5974, #5931, #5983, #6154, #6159: Internal code changes (TWGameManager, extract processMovement, allow more classes for InputFilter, Victory, move test scenarios to testresources),
diff --git a/megamek/i18n/megamek/client/messages.properties b/megamek/i18n/megamek/client/messages.properties
index 4c96ba383f0..948ae58efe7 100644
--- a/megamek/i18n/megamek/client/messages.properties
+++ b/megamek/i18n/megamek/client/messages.properties
@@ -168,6 +168,19 @@ AdvancedOptions.KeyRepeatRate.name=Key Repeat Rate
AdvancedOptions.KeyRepeatRate.tooltip= Sets how frequently a key is repeated, if a keybind has the isRepeatable flag set.
AdvancedOptions.ShowFPS.name=Show drawtime
+AdvancedSearchMapDialog.boardTableCount=Count:
+AdvancedSearchMapDialog.boardTableModel.name=Name
+AdvancedSearchMapDialog.boardTableModel.size=Size
+AdvancedSearchMapDialog.boardTagsAllCheckBox=All
+AdvancedSearchMapDialog.filterBoardPaths=Board Paths
+AdvancedSearchMapDialog.filterBoardTags=Board Tags
+AdvancedSearchMapDialog.filterName=Name:
+AdvancedSearchMapDialog.filterRangeEnd.tooltip=end range, blank acts as wildcard
+AdvancedSearchMapDialog.filterRangeHeight=Height:
+AdvancedSearchMapDialog.filterRangeStart.tooltip=start range, blank acts as wildcard
+AdvancedSearchMapDialog.filterRangeWidth=Width:
+AdvancedSearchMapDialog.title=Advanced Search
+
#Board Editor
BoardEditor.BridgeBuildingElevError=Bridge/Building Elevation is an offset from the surface of a hex and hence must be a positive value!
BoardEditor.OpenFileError=Could not open file {0}.
@@ -647,6 +660,10 @@ ChatLounge.butGroundMap = Ground Map
ChatLounge.butNames=Random Names...
ChatLounge.butRemoveBot=Remove Bot
ChatLounge.butSaveList=Save Unit List...
+ChatLounge.butPrintList=Print Unit List...
+ChatLounge.butPrintList.tooltip=Print the record sheets for the current player's units.\
+
This requires MegaMekLab to exist on your system.
+ChatLounge.butPrintList.printing=Loading print dialog
ChatLounge.butShrink=<
ChatLounge.butSkills=Random Skills...
ChatLounge.butShowUnitID=Show IDs
@@ -1270,6 +1287,10 @@ CommonSettingsDialog.logFileName=Game log filename:
CommonSettingsDialog.userDir=User Files Directory:
CommonSettingsDialog.userDir.tooltip=Use this directory for resources you want to share between different installs or versions of MegaMek, MegaMekLab and MekHQ. Fonts, units, camos, portraits and fluff images will also be loaded from this directory.
Note: Inside the user directory, use the directory structure of MM/MML/MHQ for camos, portraits and fluff images, i.e. data/images/camo, data/images/portraits and data/images/fluff/.
Fonts and units can be placed anywhere in the user directory.
CommonSettingsDialog.userDir.chooser.title=Choose User Data Folder
+CommonSettingsDialog.mmlPath=Path to MegaMekLab Executable:
+CommonSettingsDialog.mmlPath.tooltip=Used for printing unit lists.\
+
MegaMek will try to autodetect this when the option is blank if MM and MML are installed together.
+CommonSettingsDialog.mmlPath.chooser.title=Select MegaMekLab Executable
CommonSettingsDialog.main=Main
CommonSettingsDialog.audio=Audio
CommonSettingsDialog.miniMap=Mini Map
@@ -1972,6 +1993,10 @@ KeyBinds.cmdNames.undoSingleStep=Undo Single Step
KeyBinds.cmdDesc.undoSingleStep=Undo single step in movement phase
KeyBinds.cmdNames.extendTurnTimer=Activates Turn Timer Extension
KeyBinds.cmdDesc.extendTurnTimer=When Turn Timer gets to 2 seconds left, reset timer.
+KeyBinds.cmdNames.pause=Pause the Game
+KeyBinds.cmdDesc.pause=Pauses the game. Works only when only Princess players with active units remain.
+KeyBinds.cmdNames.unpause=Unpause the Game
+KeyBinds.cmdDesc.unpause=Unpauses the game
#Key Bindings Overlay
KeyBindingsDisplay.fixedBinds=Toggle Unit Display and Minimap: Mouse Button 4\nZoom: Mouse Wheel\nTurn / Torso twist: Shift + Left-Click\nLine of Sight tool: Ctrl + Left-Click (two hexes)\nRuler tool: Alt + Left-Click (two hexes)
@@ -2234,7 +2259,7 @@ MekSelectorDialog.Search.ClanEngine=Clan Engine:
MekSelectorDialog.Search.Source=Source:
MekSelectorDialog.Search.Source.TT=Multiple search tokens can be used, separated by spaces, e.g. "guide #9"
MekSelectorDialog.Search.MULId=MUL Id:
-MekSelectorDialog.Search.TroopSpace=Troop Space:
+MekSelectorDialog.Search.TroopSpace=Infantry Compartments:
MekSelectorDialog.Search.ASFBays=ASF Bays:
MekSelectorDialog.Search.SmallCraftBays=Small Craft Bays:
MekSelectorDialog.Search.MekBays=Mek Bays:
@@ -2693,7 +2718,6 @@ MovementDisplay.moveLowerElevation=Go Down
MovementDisplay.BackWardsElevationChange=Moving Backwards over an elevation change.\n
MovementDisplay.CarefulStand.title=Careful Stand?
MovementDisplay.CarefulStand.message=Do you wish to use careful stand?\n
-MovementDisplay.Traitor=Traitor Unit
MovementDisplay.NotUpToSpeed=Selected unit cannot run/flank because it has poor performance and is not up to speed
MovementDisplay.UpToSpeed=Selected unit is up to speed so it can run/flank
MovementDisplay.UnjamRAC.title=Unjam?
@@ -4626,13 +4650,135 @@ CMVPanel.copyText=Copy as Text
CMVPanel.MUL=Open MUL
CMVPanel.font=Font:
-#Gamemaster Menu Text
-Gamemaster.Gamemaster=Gamemaster
-Gamemaster.EditDamage=Edit Damage
-Gamemaster.Configure=Configure
-
# Scenario Chooser
ScenarioChooser.title=Choose Scenario
# SBF Target Dialog
SBFTargetDialog.title=Targeting
+
+
+#Gamemaster Menu Text
+Gamemaster.Gamemaster=Gamemaster
+Gamemaster.EditDamage=Edit Damage (unstable)
+Gamemaster.Configure=Configure (unstable)
+Gamemaster.Rescue=Rescue Unit
+Gamemaster.Traitor.title=Traitor
+Gamemaster.Traitor.confirm=Confirm
+Gamemaster.Traitor=Traitor Unit
+Gamemaster.Traitor.text=Traitor Unit {0}
+Gamemaster.Traitor.text.noplayers=No players available. Units cannot have their ownership passed to players that aren't assigned to a team.
+Gamemaster.Traitor.text.selectplayer=Choose the player to gain ownership of this unit, {0}, when it turns traitor
+Gamemaster.Traitor.confirmation={0} will switch to {1}'s side at the end of this turn. Are you sure?
+
+Gamemaster.dialog.confirm=Confirm
+Gamemaster.KillUnit=Kill Unit
+Gamemaster.KillUnit.text=Kill Unit {0}
+Gamemaster.KillUnit.confirmation=Are you sure you want to kill {0}?
+Gamemaster.SpecialCommands=Special Actions
+#Gamemaster Chat Commands
+Gamemaster.cmd.missingUnit=Specified unit is not on the board.
+Gamemaster.cmd.error.integerparse=must be between the min and max values:
+Gamemaster.cmd.help=Usage:
+Gamemaster.cmd.params.required=Required.
+Gamemaster.cmd.params.optional=Optional.
+Gamemaster.cmd.x=The x coordinate of the hex.
+Gamemaster.cmd.y=The y coordinate of the hex.
+# Rescue cmd
+Gamemaster.cmd.rescue.longName=Rescue Unit
+Gamemaster.cmd.rescue.unitID=ID of the unit to rescue.
+Gamemaster.cmd.rescue.help=Rescues a single unit.
+Gamemaster.cmd.rescue.success={0} was rescued.
+Gamemaster.Rescue.text=Rescue Unit {0}
+Gamemaster.Rescue.confirmation=Are you sure you want to rescue {0}?
+# Remove Smoke cmd
+Gamemaster.cmd.removesmoke.longName=Remove Smoke
+Gamemaster.cmd.removesmoke.help=Removes all smoke cloud hexes.
+Gamemaster.cmd.removesmoke.success=The air is cleaner and the smoke is gone.
+# Kill Unit cmd
+Gamemaster.cmd.kill.longName=Kill Unit
+Gamemaster.cmd.kill.unitID=ID of the unit to kill.
+Gamemaster.cmd.kill.help=Kills a single unit.
+Gamemaster.cmd.kill.success=Is going to be destroyed at the end of this phase.
+Gamemaster.cmd.kill.reason=Killed by GM.
+# Change ownership cmd
+Gamemaster.cmd.changeownership.longName=Change Unit Ownership (traitor)
+Gamemaster.cmd.changeownership.help=Changes the ownership of a unit from one player to another.
+Gamemaster.cmd.changeownership.unitID=ID of the unit to change ownership.
+Gamemaster.cmd.changeownership.playerID=ID of the player to receive the unit.
+Gamemaster.cmd.changeownership.unitNotFound=No such entity.
+Gamemaster.cmd.changeownership.playerNotFound=No such player.
+Gamemaster.cmd.changeownership.playerUnassigned=Player must be assigned a team.
+Gamemaster.cmd.changeownership.success=Ownership of unit {0} will be transferred to player {1} at the end of this round.
+# Change weather cmd
+Gamemaster.cmd.changeweather.longName=Change Planetary Conditions
+Gamemaster.cmd.changeweather.help=Change any of the planetary conditions. Effects change at the next round.
+Gamemaster.cmd.changeweather.fog=Fog
+Gamemaster.cmd.changeweather.light=Light
+Gamemaster.cmd.changeweather.wind=Wind
+Gamemaster.cmd.changeweather.winddir=Wind Direction
+Gamemaster.cmd.changeweather.atmo=Atmosphere
+Gamemaster.cmd.changeweather.blowsand=Blowing Sand
+Gamemaster.cmd.changeweather.emi=EMI
+Gamemaster.cmd.changeweather.weather=Weather
+# Disaster cmd
+Gamemaster.cmd.disaster.longName=Disaster
+Gamemaster.cmd.disaster.help=Causes a disaster on the board.
+Gamemaster.cmd.disaster.type=Type of disaster. Beware, some disasters are very destructive!
+Gamemaster.cmd.changeweather.fog.success=The fog has changed.
+Gamemaster.cmd.changeweather.wind.success=The wind strength has changed.
+Gamemaster.cmd.changeweather.winddir.success=The wind direction has changed.
+Gamemaster.cmd.changeweather.light.success=The light has changed.
+Gamemaster.cmd.changeweather.atmo.success0=The air has vanished, put your vac suits!
+Gamemaster.cmd.changeweather.atmo.success=The air is changing.
+Gamemaster.cmd.changeweather.blowsand.success1=Sand started blowing.
+Gamemaster.cmd.changeweather.blowsand.success=The sand has settled.
+Gamemaster.cmd.changeweather.weather.success=The weather has changed.
+Gamemaster.cmd.changeweather.emi.success1=EMI is active.
+Gamemaster.cmd.changeweather.emi.success=EMI is inactive.
+# Firestarter cmd
+Gamemaster.cmd.firestarter.longName=Start a Fire
+Gamemaster.cmd.fire.type=Type of fire. They are 1=Normal, 2=Inferno, 3=Inferno Bomb or 4=Inferno IV.
+Gamemaster.cmd.fire.help=Starts a fire on the board.
+# Firestorm cmd
+Gamemaster.cmd.firestorm.longName=Firestorm
+Gamemaster.cmd.firestorm.help=Starts fire in the entire board.
+Gamemaster.cmd.fire.failed=Failed to ignite fire.
+Gamemaster.cmd.fire.percent=Percentage of the board hexes to ignite.
+# Orbital bombardment cmd
+Gamemaster.cmd.orbitalbombardment.longName=Orbital Bombardment
+Gamemaster.cmd.orbitalbombardment.help=Calls an orbital bombardment on the board. It hits after the firing phase.
+Gamemaster.cmd.orbitalbombardment.dmg=Total damage at target hex.
+Gamemaster.cmd.orbitalbombardment.radius=Radius of the bombardment.
+Gamemaster.cmd.orbitalbombardment.error.outofbounds=Specified hex is not on the board.
+Gamemaster.cmd.orbitalbombardment.success=Orbital bombardment incoming!
+# Firefight
+Gamemaster.cmd.firefight.longName=Firefight
+Gamemaster.cmd.firefight.reason=Fire extinguished
+Gamemaster.cmd.firefight.help=Extinguishes a fire on the board.
+# No Fire
+Gamemaster.cmd.nofire.longName=No Fires
+Gamemaster.cmd.nofire.help=Extinguishes all fires on the board.
+
+# Orbital Bombardment text
+OrbitalBombardment.source=Unknown warship in orbit
+OrbitalBombardment.hitOnRound=Orbital bombardment incoming, hit on round {0}
+
+# Nuke text
+Nuke.hitOnRound=Nuke incoming, hit on round {0}
+Nuke.exploded=Dangerous radiation levels detected in the area of the nuke explosion.
+
+# Bot Contextual Menu commands
+Bot.commands.title=Bot Commands
+Bot.commands.targetHex=Target Location
+Bot.commands.priority=Priority Target Unit
+Bot.commands.ignore=Ignore Unit
+Bot.commands.flee=Flee
+Bot.commands.flee.text=Flee now!
+Bot.commands.flee.confirmation=Are you sure you want to order {0} to flee?
+Bot.commands.flee.confirm=Confirm
+Bot.commands.behavior=Behavior Settings
+Bot.commands.herding=Herding
+Bot.commands.aggression=Aggression
+Bot.commands.bravery=Bravery
+Bot.commands.avoid=Self-Preservation
+Bot.commands.caution=Piloting Caution
\ No newline at end of file
diff --git a/megamek/i18n/megamek/client/messages_es.properties b/megamek/i18n/megamek/client/messages_es.properties
index 086d314aee1..98e1e7aeb78 100644
--- a/megamek/i18n/megamek/client/messages_es.properties
+++ b/megamek/i18n/megamek/client/messages_es.properties
@@ -2438,7 +2438,6 @@ MovementDisplay.moveLowerElevation=Bajar
MovementDisplay.BackWardsElevationChange=Moverse hacia atrás sobre un cambio de elevación.\n
MovementDisplay.CarefulStand.title=¿Mantenerse con Precaución?
MovementDisplay.CarefulStand.message=¿Desea Mantenerse con Precaución?\n
-MovementDisplay.Traitor=Unidad Traidora
MovementDisplay.NotUpToSpeed=La unidad seleccionada no puede correr/flanquear porque tiene un rendimiento deficiente y no está al día
MovementDisplay.UpToSpeed=La unidad seleccionada está al día para que pueda correr/flanquear
MovementDisplay.UnjamRAC.title=¿Destrabar?
@@ -4267,3 +4266,5 @@ ForceGeneratorDialog.reinforced=Reforzado\
ForceGeneratorDialog.understrength=Más Débiles\
ForceGeneratorDialog.default=Predeterminado
ForceGeneratorDialog.noCrew=Sin tripulación
+
+Gamemaster.Traitor=Unidad Traidora
\ No newline at end of file
diff --git a/megamek/i18n/megamek/client/messages_ru.properties b/megamek/i18n/megamek/client/messages_ru.properties
index 06812521aca..69f44bd0bed 100644
--- a/megamek/i18n/megamek/client/messages_ru.properties
+++ b/megamek/i18n/megamek/client/messages_ru.properties
@@ -1373,7 +1373,6 @@ MovementDisplay.moveLowerElevation=Спуститься
MovementDisplay.BackWardsElevationChange=Преодоление смены уровня высоты реверсом хода.\n
MovementDisplay.CarefulStand.title=Использовать осторожную стойку?
MovementDisplay.CarefulStand.message=Хотите ли вы использовать осторожную стойку?\n
-MovementDisplay.Traitor=Юнит предатель
MovementDisplay.NotUpToSpeed=Выбранный юнит не может бежать/ехать быстро потому что у него плохая форма и он не достигнет скорости
MovementDisplay.UpToSpeed=Выбранному юниту хватает скорости и он может бежать/ехать быстро
MovementDisplay.UnjamRAC.title=Расклинить?
@@ -2258,3 +2257,5 @@ BotConfigDialog.princessHelp.title=Справка по Принцессе
SavePrincessDialog.question=Сохранить изменения в эту конфигурацию?
SavePrincessDialog.saveTargets=Сохранить список стратегичеких целей?
+
+Gamemaster.Traitor=Юнит предатель
\ No newline at end of file
diff --git a/megamek/i18n/megamek/common/equipmentmessages.properties b/megamek/i18n/megamek/common/equipmentmessages.properties
index 4f4b6444a7a..7fc53ebc1ff 100644
--- a/megamek/i18n/megamek/common/equipmentmessages.properties
+++ b/megamek/i18n/megamek/common/equipmentmessages.properties
@@ -16,6 +16,7 @@ EquipmentMode.Off=Off
EquipmentMode.Armed=Armed
EquipmentMode.Normal=Normal
EquipmentMode.Aimed\ shot=Aimed shot
+EquipmentMode.Pulse=Pulse
EquipmentMode.Bracket\ 80%=Bracket 80%
EquipmentMode.Bracket\ 60%=Bracket 60%
diff --git a/megamek/i18n/megamek/common/report-messages.properties b/megamek/i18n/megamek/common/report-messages.properties
index c7431827b9c..b82e7e2a3aa 100755
--- a/megamek/i18n/megamek/common/report-messages.properties
+++ b/megamek/i18n/megamek/common/report-messages.properties
@@ -55,6 +55,12 @@
1241=Blue Shield Field Dampener fails!
1242=no effect
+#1300s - Orbital Bombardment related
+1300=Orbital bombardment hit hex !!!
+1301=End of orbital bombardment resolution
+1302= has commenced an orbital bombardment, it will land at the end of the next weapons phase at hex .
+1303=Start of orbital bombardment resolution
+
# 1500s - ammo handling related
1500= () selected
1501= due to out of ammo!
diff --git a/megamek/mmconf/defaultKeyBinds.xml b/megamek/mmconf/defaultKeyBinds.xml
index 2df36f7a237..7b0ecbd7599 100644
--- a/megamek/mmconf/defaultKeyBinds.xml
+++ b/megamek/mmconf/defaultKeyBinds.xml
@@ -1,6 +1,6 @@
+ xsi:noNamespaceSchemaLocation="keyBindingSchema.xsd">
scrollN
87
@@ -78,6 +78,20 @@
false
+
+ pause
+ 80
+ 192
+ false
+
+
+
+ unpause
+ 80
+ 640
+ false
+
+
nextWeapon
40
diff --git a/megamek/src/megamek/Version.java b/megamek/src/megamek/Version.java
index d422ce849b6..b065b72649f 100644
--- a/megamek/src/megamek/Version.java
+++ b/megamek/src/megamek/Version.java
@@ -284,10 +284,10 @@ public void fillFromText(final @Nullable String text) {
@Override
public String toString() {
return String.format(
- "%d.%d.%d%s",
- getRelease(),
- getMajor(),
- getMinor(),
- (isSnapshot() ? "-SNAPSHOT" : ""));
+ "%d.%02d.%02d%s",
+ getRelease(),
+ getMajor(),
+ getMinor(),
+ (isSnapshot() ? "-SNAPSHOT" : ""));
}
}
diff --git a/megamek/src/megamek/client/AbstractClient.java b/megamek/src/megamek/client/AbstractClient.java
index 6a30c1c5ace..4f944b39c4c 100644
--- a/megamek/src/megamek/client/AbstractClient.java
+++ b/megamek/src/megamek/client/AbstractClient.java
@@ -235,6 +235,16 @@ public synchronized void sendDone(boolean done) {
flushConn();
}
+ public void sendPause() {
+ send(new Packet(PacketCommand.PAUSE));
+ flushConn();
+ }
+
+ public void sendUnpause() {
+ send(new Packet(PacketCommand.UNPAUSE));
+ flushConn();
+ }
+
/**
* Receives player information from the message packet.
*
diff --git a/megamek/src/megamek/client/bot/princess/FireControl.java b/megamek/src/megamek/client/bot/princess/FireControl.java
index 54125489fad..70f228ab23b 100644
--- a/megamek/src/megamek/client/bot/princess/FireControl.java
+++ b/megamek/src/megamek/client/bot/princess/FireControl.java
@@ -902,8 +902,8 @@ ToHitData guessToHitModifierForWeapon(final Entity shooter,
toHit.append(getDamageWeaponMods(shooter, weapon));
// weapon mods
- if (0 != weaponType.getToHitModifier()) {
- toHit.addModifier(weaponType.getToHitModifier(), TH_WEAPON_MOD);
+ if (0 != weaponType.getToHitModifier(weapon)) {
+ toHit.addModifier(weaponType.getToHitModifier(weapon), TH_WEAPON_MOD);
}
// Target size.
@@ -3475,7 +3475,7 @@ AmmoMounted getAntiAirAmmo(final List ammoList,
if (!(weaponType instanceof MMLWeapon)) {
// Naively assume that easier-hitting is better
if (returnAmmo != null) {
- AmmoType returnAmmoType = (AmmoType) (returnAmmo.getType());
+ AmmoType returnAmmoType = returnAmmo.getType();
returnAmmo = ((ammoType.getToHitModifier() > returnAmmoType.getToHitModifier()) ? ammo
: returnAmmo);
} else {
diff --git a/megamek/src/megamek/client/ui/advancedSearchMap/AdvancedSearchMapDialog.java b/megamek/src/megamek/client/ui/advancedSearchMap/AdvancedSearchMapDialog.java
new file mode 100644
index 00000000000..52bcf0c318e
--- /dev/null
+++ b/megamek/src/megamek/client/ui/advancedSearchMap/AdvancedSearchMapDialog.java
@@ -0,0 +1,381 @@
+/*
+ * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved.
+ *
+ * This file is part of MegaMek.
+ *
+ * MegaMek is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MegaMek is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MegaMek. If not, see .
+ */
+package megamek.client.ui.advancedSearchMap;
+
+import megamek.client.ui.Messages;
+import megamek.client.ui.baseComponents.AbstractButtonDialog;
+import megamek.client.ui.swing.util.UIUtil;
+import megamek.common.util.StringUtil;
+import megamek.utilities.BoardClassifier;
+import megamek.utilities.BoardsTagger;
+
+import javax.swing.*;
+import javax.swing.event.DocumentEvent;
+import javax.swing.event.DocumentListener;
+import javax.swing.event.ListSelectionEvent;
+import javax.swing.event.ListSelectionListener;
+import javax.swing.table.DefaultTableCellRenderer;
+import javax.swing.table.TableRowSorter;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.PatternSyntaxException;
+
+/**
+ * This is the dialog for advanced map filtering
+ */
+public class AdvancedSearchMapDialog extends AbstractButtonDialog {
+ private BoardClassifier bc;
+ private JTable boardTable = new JTable() {
+ @Override
+ public Dimension getPreferredScrollableViewportSize() {
+ Dimension standardSize = super.getPreferredScrollableViewportSize();
+ return new Dimension(standardSize.width, getRowHeight() * 20);
+ }
+ };
+ private JList listBoardTags = new JList<>();
+ private final JCheckBox boardTagsAllCheckBox = new JCheckBox(Messages.getString("AdvancedSearchMapDialog.boardTagsAllCheckBox"));
+ private JList listBoardPaths = new JList<>();
+ private JLabel boardImage;
+ private JLabel boardInfo;
+ private TableRowSorter boardSorter = new TableRowSorter<>();
+ private BoardTableModel boardModel = new BoardTableModel();
+ private JLabel boardCountLabel = new JLabel("");
+ private JTextField widthStartTextField = new JTextField(4);
+ private JTextField widthEndTextField = new JTextField(4);
+ private JTextField heightStartTextField = new JTextField(4);
+ private JTextField heightEndTextField = new JTextField(4);
+ private JTextField nameTextField = new JTextField(4);
+
+ public AdvancedSearchMapDialog(JFrame parent) {
+ super(parent, true, "AdvancedSearchMapDialog", "AdvancedSearchMapDialog.title");
+
+ setPreferredSize(new Dimension(1200, 1600));
+
+ initialize();
+ }
+
+ @Override
+ protected Container createCenterPane() {
+ bc = BoardClassifier.getInstance();
+
+ JPanel advancedSearchPane = new JPanel(new GridBagLayout());
+ GridBagConstraints c = new GridBagConstraints();
+ c.gridwidth = 1;
+ c.gridheight = 1;
+ c.weightx = 0;
+ c.weighty = 0;
+ c.fill = GridBagConstraints.BOTH;
+ c.anchor = GridBagConstraints.NORTHWEST;
+ c.insets = new Insets(2, 2, 2, 2);
+ c.gridx = 0;
+ c.gridy = 0;
+
+ advancedSearchPane.add(createInfo(), c);
+
+ JPanel mainPanel = new JPanel(new FlowLayout());
+ mainPanel.add(createFilter());
+ mainPanel.add(createListTable());
+
+ c.gridy++;
+
+ advancedSearchPane.add(mainPanel, c);
+
+ return new JScrollPane(advancedSearchPane);
+ }
+
+ private JPanel createInfo() {
+ JPanel infoPanel = new JPanel(new FlowLayout());
+ boardImage = new JLabel();
+ infoPanel.add(boardImage);
+ boardInfo = new JLabel();
+ infoPanel.add(boardInfo);
+ return infoPanel;
+ }
+
+ private JPanel createFilter() {
+ JPanel filterPanel = new JPanel();
+ filterPanel.setLayout(new BoxLayout(filterPanel, BoxLayout.PAGE_AXIS));
+
+ filterPanel.add(createFilterRange(widthStartTextField, widthEndTextField, Messages.getString("AdvancedSearchMapDialog.filterRangeWidth")));
+ filterPanel.add(createFilterRange(heightStartTextField, heightEndTextField, Messages.getString("AdvancedSearchMapDialog.filterRangeHeight")));
+ filterPanel.add(createFilterText(nameTextField, Messages.getString("AdvancedSearchMapDialog.filterName")));
+
+ JPanel tagsTitlePanel = createTitleWithCheckBox(boardTagsAllCheckBox, Messages.getString("AdvancedSearchMapDialog.filterBoardTags"));
+ List tags = Arrays.stream(BoardsTagger.Tags.values()).map(BoardsTagger.Tags::getName).distinct().sorted().toList();
+ filterPanel.add(createFilterList(listBoardTags, tags, tagsTitlePanel, true));
+
+ JPanel pathsTitlePanel = createTitle(Messages.getString("AdvancedSearchMapDialog.filterBoardPaths"));
+ List paths = bc.getBoardPaths().values().stream().toList();
+ paths = paths.stream().map(p -> p.substring(0, p.lastIndexOf("\\") + 1 )).distinct().sorted().toList();
+ filterPanel.add(createFilterList(listBoardPaths, paths, pathsTitlePanel, false));
+
+ return filterPanel;
+ }
+
+ private JPanel createFilterText(JTextField textField, String caption) {
+ JPanel textPanel = new JPanel(new BorderLayout());
+ Box textBox = new Box(BoxLayout.LINE_AXIS);
+ textBox.setAlignmentX(Component.LEFT_ALIGNMENT);
+
+ textBox.add(new JLabel(caption));
+ textBox.add(Box.createRigidArea( new Dimension(5, 0)));
+ textField.getDocument().addDocumentListener(new DocumentListener() {
+ @Override
+ public void insertUpdate(DocumentEvent e) {
+ filterTables();
+ }
+
+ @Override
+ public void removeUpdate(DocumentEvent e) {
+ filterTables();
+ }
+
+ @Override
+ public void changedUpdate(DocumentEvent e) {
+ filterTables();
+ }
+ });
+ textBox.add(textField);
+
+ textPanel.add(textBox);
+
+ return textPanel;
+ }
+
+ private JPanel createFilterRange(JTextField startTextField, JTextField endTextField, String caption) {
+ JPanel textPanel = new JPanel(new BorderLayout());
+
+ Box textBox = new Box(BoxLayout.LINE_AXIS);
+ textBox.setAlignmentX(Component.LEFT_ALIGNMENT);
+
+ textBox.add(new JLabel(caption));
+ textBox.add(Box.createRigidArea( new Dimension(5, 0)));
+ startTextField.setToolTipText(Messages.getString("AdvancedSearchMapDialog.filterRangeStart.tooltip"));
+ startTextField.getDocument().addDocumentListener(new DocumentListener() {
+ @Override
+ public void insertUpdate(DocumentEvent e) {
+ filterTables();
+ }
+
+ @Override
+ public void removeUpdate(DocumentEvent e) {
+ filterTables();
+ }
+
+ @Override
+ public void changedUpdate(DocumentEvent e) {
+ filterTables();
+ }
+ });
+ textBox.add(startTextField);
+
+ textBox.add(new JLabel(" - "));
+
+ endTextField.setToolTipText(Messages.getString("AdvancedSearchMapDialog.filterRangeEnd.tooltip"));
+ endTextField.getDocument().addDocumentListener(new DocumentListener() {
+ @Override
+ public void insertUpdate(DocumentEvent e) {
+ filterTables();
+ }
+
+ @Override
+ public void removeUpdate(DocumentEvent e) {
+ filterTables();
+ }
+
+ @Override
+ public void changedUpdate(DocumentEvent e) {
+ filterTables();
+ }
+ });
+ textBox.add(endTextField);
+
+ textPanel.add(textBox, BorderLayout.NORTH);
+ return textPanel;
+ }
+
+ private JPanel createTitleWithCheckBox(JCheckBox checkBox, String caption) {
+ JPanel titlePanel = new JPanel(new BorderLayout());
+
+ Box titleBox = new Box(BoxLayout.LINE_AXIS);
+ titleBox.setAlignmentX(Component.LEFT_ALIGNMENT);
+ titleBox.add(new JLabel(caption));
+ titleBox.add(Box.createRigidArea( new Dimension(5, 0)));
+ checkBox.setSelected(false);
+ checkBox.addActionListener(new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ filterTables();
+ }
+ });
+ titleBox.add(checkBox);
+ titlePanel.add(titleBox, BorderLayout.NORTH);
+
+ return titlePanel;
+ }
+
+ private JPanel createTitle(String caption) {
+ JPanel titlePanel = new JPanel(new BorderLayout());
+
+ Box titleBox = new Box(BoxLayout.LINE_AXIS);
+ titleBox.setAlignmentX(Component.LEFT_ALIGNMENT);
+ titleBox.add(new JLabel(caption));
+ titlePanel.add(titleBox, BorderLayout.NORTH);
+
+ return titlePanel;
+ }
+
+ private JPanel createFilterList(JList list, List data, JPanel title, boolean selectAll) {
+ DefaultListModel model = new DefaultListModel<>();
+ model.addAll(data);
+ list.setModel(model);
+ list.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
+ list.setSelectedIndex(0);
+ list.addListSelectionListener (new ListSelectionListener() {
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ filterTables();
+ }
+ });
+
+ if (selectAll) {
+ list.addSelectionInterval(0, data.size());
+ }
+
+ JPanel boardPathsPanel = new JPanel(new BorderLayout());
+ boardPathsPanel.add(title, BorderLayout.NORTH);
+ boardPathsPanel.add(new JScrollPane(list), BorderLayout.CENTER);
+
+ return boardPathsPanel;
+ }
+
+ private JPanel createListTable() {
+ JPanel listPanel = new JPanel();
+ listPanel.setLayout(new BoxLayout(listPanel, BoxLayout.PAGE_AXIS));
+
+ boardModel.setData(bc);
+ boardTable.setName("Board");
+ ListSelectionModel boardSelModel = boardTable.getSelectionModel();
+ boardSelModel.addListSelectionListener (new ListSelectionListener() {
+ @Override
+ public void valueChanged(ListSelectionEvent e) {
+ int index = boardTable.getSelectedRow() ;
+ if (index >= 0) {
+ index = boardTable.convertRowIndexToModel(index);
+ boardImage.setIcon(boardModel.getIconAt(index, UIUtil.scaleForGUI(200)));
+ boardInfo.setText(boardModel.getInfoAt(index));
+ }
+ }
+ });
+ boardTable.setModel(boardModel);
+ boardSorter.setModel(boardModel);
+ boardSorter.setSortKeys(List.of(new RowSorter.SortKey(0, SortOrder.ASCENDING)));
+ boardTable.setRowSorter(boardSorter);
+ boardTable.setIntercellSpacing(new Dimension(5, 0));
+ boardTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
+ for (int i = 0; i < boardModel.getColumnCount(); i++) {
+ boardTable.getColumnModel().getColumn(i).setPreferredWidth(boardModel.getPreferredWidth(i));
+ }
+ boardTable.setRowSelectionInterval(0,0);
+ DefaultTableCellRenderer rightRenderer = new DefaultTableCellRenderer();
+ rightRenderer.setHorizontalAlignment(JLabel.RIGHT);
+ boardTable.setFillsViewportHeight(true);
+ listPanel.add(new JScrollPane(boardTable));
+
+ JPanel countPanel = new JPanel(new FlowLayout());
+ JLabel countLabel = new JLabel(Messages.getString("AdvancedSearchMapDialog.boardTableCount"));
+ countPanel.add(countLabel);
+
+ boardCountLabel.setText(boardModel.getRowCount() + "");
+ countPanel.add(boardCountLabel);
+ listPanel.add(countPanel);
+
+ return listPanel;
+ }
+
+ private void filterTables() {
+ RowFilter boardFilter;
+ try {
+ boardFilter = new RowFilter<>() {
+ @Override
+ public boolean include(Entry extends BoardTableModel, ? extends Integer> entry) {
+ BoardTableModel eqModel = entry.getModel();
+
+ String path = eqModel.getPathAt(entry.getIdentifier());
+ boolean pathMatch = matchPath(path);
+
+ List tags = eqModel.getTagAt(entry.getIdentifier());
+ boolean tagMatch = matchTag(tags);
+
+ boolean widthMatch = StringUtil.isBetween(eqModel.getWidthAt(entry.getIdentifier()), widthStartTextField.getText(), widthEndTextField.getText());
+
+ boolean heightMatch = StringUtil.isBetween(eqModel.getHeightAt(entry.getIdentifier()), heightStartTextField.getText(), heightEndTextField.getText());
+
+ boolean nameMatch = eqModel.getPathAt(entry.getIdentifier()).toUpperCase().contains(nameTextField.getText().toUpperCase());
+
+ return pathMatch && tagMatch && widthMatch && heightMatch && nameMatch;
+ }
+ };
+ } catch (PatternSyntaxException ignored) {
+ return;
+ }
+
+ boardSorter.setRowFilter(boardFilter);
+ boardCountLabel.setText(boardTable.getRowCount() + "");
+
+ if (boardTable.getRowCount() > 0) {
+ boardTable.setRowSelectionInterval(0, 0);
+ boardTable.scrollRectToVisible(boardTable.getCellRect(0, 0, true));
+ }
+ }
+
+ private boolean matchPath(String path) {
+ List include = listBoardPaths.getSelectedValuesList();
+
+ String value = path.substring(0, path.lastIndexOf("\\") + 1 );
+
+ return !include.isEmpty() && include.stream().anyMatch(value::contains);
+ }
+
+ private boolean matchTag(List tags) {
+ List include = listBoardTags.getSelectedValuesList();
+
+ if (boardTagsAllCheckBox.isSelected()) {
+ return !include.isEmpty() && include.stream().allMatch(tags::contains);
+ } else {
+ return !include.isEmpty() && include.stream().anyMatch(tags::contains);
+ }
+ }
+
+ /**
+ * Returns if path for the selected map when the dialog is confirmed
+ */
+ public String getPath() {
+ int index = boardTable.getSelectedRow() ;
+ if (index >= 0) {
+ index = boardTable.convertRowIndexToModel(index);
+ String path = boardModel.getPathAt(index);
+ return path;
+ }
+
+ return null;
+ }
+}
diff --git a/megamek/src/megamek/client/ui/advancedSearchMap/BoardTableModel.java b/megamek/src/megamek/client/ui/advancedSearchMap/BoardTableModel.java
new file mode 100644
index 00000000000..fb74f8ef95b
--- /dev/null
+++ b/megamek/src/megamek/client/ui/advancedSearchMap/BoardTableModel.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (c) 2024 - The MegaMek Team. All Rights Reserved.
+ *
+ * This file is part of MegaMek.
+ *
+ * MegaMek is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * MegaMek is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with MegaMek. If not, see .
+ */
+package megamek.client.ui.advancedSearchMap;
+
+import megamek.client.ui.Messages;
+import megamek.client.ui.swing.minimap.Minimap;
+import megamek.client.ui.swing.util.UIUtil;
+import megamek.common.*;
+import megamek.common.util.ImageUtil;
+import megamek.common.util.fileUtils.MegaMekFile;
+import megamek.utilities.BoardClassifier;
+
+import javax.swing.*;
+import javax.swing.table.AbstractTableModel;
+import java.awt.image.BufferedImage;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * A table model for the advanced map search
+ */
+class BoardTableModel extends AbstractTableModel {
+ private static final int COL_NAME = 0;
+ private static final int COL_SIZE = 1;
+ private static final int N_COL = 2;
+
+ private List data;
+ private List tags;
+ private List width;
+ private List height;
+
+ public BoardTableModel() {
+ data = new ArrayList<>();
+ }
+
+ @Override
+ public int getRowCount() {
+ return data.size();
+ }
+
+ @Override
+ public int getColumnCount() {
+ return N_COL;
+ }
+
+ @Override
+ public String getColumnName(int column) {
+ switch (column) {
+ case COL_NAME:
+ return Messages.getString("AdvancedSearchMapDialog.boardTableModel.name");
+ case COL_SIZE:
+ return Messages.getString("AdvancedSearchMapDialog.boardTableModel.size");
+ default:
+ return "??";
+ }
+ }
+
+ @Override
+ public Class> getColumnClass(int c) {
+ return getValueAt(0, c).getClass();
+ }
+
+ @Override
+ public boolean isCellEditable(int row, int col) {
+ return false;
+ }
+
+ @Override
+ public Object getValueAt(int row, int col) {
+ String path = getPathAt(row);
+ Integer width = getWidthAt(row);
+ Integer height = getHeightAt(row);
+
+ if (path== null) {
+ return "?";
+ }
+ String value = "";
+
+ if (col == COL_NAME) {
+ value = path.substring(path.lastIndexOf("\\") + 1, path.length());
+ value = value.substring(0, value.lastIndexOf(".board"));
+ value = value.replace(width + "x" + height, "").trim();
+ } else if (col == COL_SIZE) {
+ value = String.format("%03dx%03d", width, height);
+ }
+
+ return value;
+ }
+
+ public int getPreferredWidth(int col) {
+ return switch (col) {
+ case COL_NAME -> 200;
+ case COL_SIZE -> 20;
+ default -> 10;
+ };
+ }
+
+ public void setData(BoardClassifier bc) {
+ data = bc.getBoardPaths().values().stream().toList();;
+ tags = new ArrayList<>();
+ width = new ArrayList<>();
+ height = new ArrayList<>();
+
+ for (String path : data) {
+ String key = Configuration.boardsDir() + path;
+ tags.add(bc.getBoardTags().get(key));
+ width.add(bc.getBoardWidth().get(key));
+ height.add(bc.getBoardHeigth().get(key));
+ }
+
+ fireTableDataChanged();
+ }
+
+ public String getPathAt(int row) {
+ if (data.size() <= row) {
+ return null;
+ }
+
+ return data.get(row);
+ }
+
+ public Integer getWidthAt(int row) {
+ if (width.size() <= row) {
+ return null;
+ }
+
+ return width.get(row);
+ }
+
+ public Integer getHeightAt(int row) {
+ if (height.size() <= row) {
+ return null;
+ }
+
+ return height.get(row);
+ }
+
+ public List getTagAt(int row) {
+ if (tags.size() <= row) {
+ return null;
+ }
+
+ String tag = tags.get(row);
+ tag = tag.substring(1, tag.length() -1);
+ return Arrays.stream(tag.split(", ")).toList();
+ }
+
+ public ImageIcon getIconAt(int row, int height) {
+ ImageIcon icon = null;
+ String path = getPathAt(row);
+
+ if (path != null) {
+ Board board = new Board(16, 17);
+ board.load(new MegaMekFile(Configuration.boardsDir(), path).getFile());
+
+ BufferedImage image = Minimap.getMinimapImageMaxZoom(board);
+
+ int scaledHeight = Math.min(image.getHeight(), height);
+ int scaledWidth = Math.max(1, image.getWidth() * scaledHeight / image.getHeight());
+
+ image = ImageUtil.getScaledImage(image, scaledWidth, scaledHeight);
+ icon = new ImageIcon(image);
+ }
+
+ return icon;
+ }
+
+ public String getInfoAt(int row) {
+ String info = "";
+ String path = getPathAt(row);
+
+ if (path != null) {
+ Board board = new Board(16, 17);
+ board.load(new MegaMekFile(Configuration.boardsDir(), path).getFile());
+
+
+ String col = UIUtil.tag("TD", "", path);
+ info = UIUtil.tag("TR", "", col);
+ col = UIUtil.tag("TD", "", board.getWidth() + "x" + board.getHeight());
+ info += UIUtil.tag("TR", "", col);
+ col = UIUtil.tag("TD", "", board.getTags().toString());
+ info += UIUtil.tag("TR", "", col);
+ info = UIUtil.tag("TABLE", "", info);
+ String attr = String.format("WIDTH=%s", UIUtil.scaleForGUI(500));
+ info = UIUtil.tag("DIV", attr, info);
+ info = UIUtil.tag("BODY", "", info);
+ info = UIUtil.tag("HTML", "", info);
+ }
+
+ return info;
+ }
+}
diff --git a/megamek/src/megamek/client/ui/advancedsearch/MekSearchFilter.java b/megamek/src/megamek/client/ui/advancedsearch/MekSearchFilter.java
index 490fcd74c9c..631dfddc3b8 100644
--- a/megamek/src/megamek/client/ui/advancedsearch/MekSearchFilter.java
+++ b/megamek/src/megamek/client/ui/advancedsearch/MekSearchFilter.java
@@ -342,17 +342,6 @@ public String getEquipmentExpression() {
return equipmentCriteria.toString();
}
- private static boolean isBetween(double value, String sStart, String sEnd) {
- if (sStart.isEmpty() && sEnd.isEmpty()) {
- return true;
- }
-
- int iStart = StringUtil.toInt(sStart, Integer.MIN_VALUE);
- int iEnd = StringUtil.toInt(sEnd, Integer.MAX_VALUE);
-
- return (!(value < iStart)) && (!(value > iEnd));
- }
-
private static boolean isMatch(int i, boolean b) {
if (i == 1) {
return b;
@@ -493,179 +482,179 @@ public static boolean isMatch(MekSummary mek, MekSearchFilter f) {
}
// Check walk criteria
- if (!isBetween(mek.getWalkMp(), f.sStartWalk, f.sEndWalk)) {
+ if (!StringUtil.isBetween(mek.getWalkMp(), f.sStartWalk, f.sEndWalk)) {
return false;
}
// Check jump criteria
- if (!isBetween(mek.getJumpMp(), f.sStartJump, f.sEndJump)) {
+ if (!StringUtil.isBetween(mek.getJumpMp(), f.sStartJump, f.sEndJump)) {
return false;
}
// Check year criteria
- if (!isBetween(mek.getYear(), f.sStartYear, f.sEndYear)) {
+ if (!StringUtil.isBetween(mek.getYear(), f.sStartYear, f.sEndYear)) {
return false;
}
// Check Tonnage criteria
- if (!isBetween((int) mek.getTons(), f.sStartTons, f.sEndTons)) {
+ if (!StringUtil.isBetween((int) mek.getTons(), f.sStartTons, f.sEndTons)) {
return false;
}
// Check BV criteria
- if (!isBetween(mek.getBV(), f.sStartBV, f.sEndBV)) {
+ if (!StringUtil.isBetween(mek.getBV(), f.sStartBV, f.sEndBV)) {
return false;
}
- if (!isBetween(mek.getTankTurrets(), f.sStartTankTurrets, f.sEndTankTurrets)) {
+ if (!StringUtil.isBetween(mek.getTankTurrets(), f.sStartTankTurrets, f.sEndTankTurrets)) {
return false;
}
- if (!isBetween(mek.getLowerArms(), f.sStartLowerArms, f.sEndLowerArms)) {
+ if (!StringUtil.isBetween(mek.getLowerArms(), f.sStartLowerArms, f.sEndLowerArms)) {
return false;
}
- if (!isBetween(mek.getHands(), f.sStartHands, f.sEndHands)) {
+ if (!StringUtil.isBetween(mek.getHands(), f.sStartHands, f.sEndHands)) {
return false;
}
- if (!isBetween(mek.getTroopCarryingSpace(), f.sStartTroopSpace, f.sEndTroopSpace)) {
+ if (!StringUtil.isBetween(mek.getTroopCarryingSpace(), f.sStartTroopSpace, f.sEndTroopSpace)) {
return false;
}
- if (!isBetween(mek.getASFBays(), f.sStartASFBays, f.sEndASFBays)) {
+ if (!StringUtil.isBetween(mek.getASFBays(), f.sStartASFBays, f.sEndASFBays)) {
return false;
}
- if (!isBetween(mek.getASFDoors(), f.sStartASFDoors, f.sEndASFDoors)) {
+ if (!StringUtil.isBetween(mek.getASFDoors(), f.sStartASFDoors, f.sEndASFDoors)) {
return false;
}
- if (!isBetween(mek.getASFUnits(), f.sStartASFUnits, f.sEndASFUnits)) {
+ if (!StringUtil.isBetween(mek.getASFUnits(), f.sStartASFUnits, f.sEndASFUnits)) {
return false;
}
- if (!isBetween(mek.getSmallCraftBays(), f.sStartSmallCraftBays, f.sEndSmallCraftBays)) {
+ if (!StringUtil.isBetween(mek.getSmallCraftBays(), f.sStartSmallCraftBays, f.sEndSmallCraftBays)) {
return false;
}
- if (!isBetween(mek.getSmallCraftDoors(), f.sStartSmallCraftDoors, f.sEndSmallCraftDoors)) {
+ if (!StringUtil.isBetween(mek.getSmallCraftDoors(), f.sStartSmallCraftDoors, f.sEndSmallCraftDoors)) {
return false;
}
- if (!isBetween(mek.getSmallCraftUnits(), f.sStartSmallCraftUnits, f.sEndSmallCraftUnits)) {
+ if (!StringUtil.isBetween(mek.getSmallCraftUnits(), f.sStartSmallCraftUnits, f.sEndSmallCraftUnits)) {
return false;
}
- if (!isBetween(mek.getMekBays(), f.sStartMekBays, f.sEndMekBays)) {
+ if (!StringUtil.isBetween(mek.getMekBays(), f.sStartMekBays, f.sEndMekBays)) {
return false;
}
- if (!isBetween(mek.getMekDoors(), f.sStartMekDoors, f.sEndMekDoors)) {
+ if (!StringUtil.isBetween(mek.getMekDoors(), f.sStartMekDoors, f.sEndMekDoors)) {
return false;
}
- if (!isBetween(mek.getMekUnits(), f.sStartMekUnits, f.sEndMekUnits)) {
+ if (!StringUtil.isBetween(mek.getMekUnits(), f.sStartMekUnits, f.sEndMekUnits)) {
return false;
}
- if (!isBetween(mek.getHeavyVehicleBays(), f.sStartHeavyVehicleBays, f.sEndHeavyVehicleBays)) {
+ if (!StringUtil.isBetween(mek.getHeavyVehicleBays(), f.sStartHeavyVehicleBays, f.sEndHeavyVehicleBays)) {
return false;
}
- if (!isBetween(mek.getHeavyVehicleDoors(), f.sStartHeavyVehicleDoors, f.sEndHeavyVehicleDoors)) {
+ if (!StringUtil.isBetween(mek.getHeavyVehicleDoors(), f.sStartHeavyVehicleDoors, f.sEndHeavyVehicleDoors)) {
return false;
}
- if (!isBetween(mek.getHeavyVehicleUnits(), f.sStartHeavyVehicleUnits, f.sEndHeavyVehicleUnits)) {
+ if (!StringUtil.isBetween(mek.getHeavyVehicleUnits(), f.sStartHeavyVehicleUnits, f.sEndHeavyVehicleUnits)) {
return false;
}
- if (!isBetween(mek.getLightVehicleBays(), f.sStartLightVehicleBays, f.sEndLightVehicleBays)) {
+ if (!StringUtil.isBetween(mek.getLightVehicleBays(), f.sStartLightVehicleBays, f.sEndLightVehicleBays)) {
return false;
}
- if (!isBetween(mek.getLightVehicleDoors(), f.sStartLightVehicleDoors, f.sEndLightVehicleDoors)) {
+ if (!StringUtil.isBetween(mek.getLightVehicleDoors(), f.sStartLightVehicleDoors, f.sEndLightVehicleDoors)) {
return false;
}
- if (!isBetween(mek.getLightVehicleUnits(), f.sStartLightVehicleUnits, f.sEndLightVehicleUnits)) {
+ if (!StringUtil.isBetween(mek.getLightVehicleUnits(), f.sStartLightVehicleUnits, f.sEndLightVehicleUnits)) {
return false;
}
- if (!isBetween(mek.getProtoMekBays(), f.sStartProtomekBays, f.sEndProtomekBays)) {
+ if (!StringUtil.isBetween(mek.getProtoMekBays(), f.sStartProtomekBays, f.sEndProtomekBays)) {
return false;
}
- if (!isBetween(mek.getProtoMekDoors(), f.sStartProtomekDoors, f.sEndProtomekDoors)) {
+ if (!StringUtil.isBetween(mek.getProtoMekDoors(), f.sStartProtomekDoors, f.sEndProtomekDoors)) {
return false;
}
- if (!isBetween(mek.getProtoMekUnits(), f.sStartProtomekUnits, f.sEndProtomekUnits)) {
+ if (!StringUtil.isBetween(mek.getProtoMekUnits(), f.sStartProtomekUnits, f.sEndProtomekUnits)) {
return false;
}
- if (!isBetween(mek.getBattleArmorBays(), f.sStartBattleArmorBays, f.sEndBattleArmorBays)) {
+ if (!StringUtil.isBetween(mek.getBattleArmorBays(), f.sStartBattleArmorBays, f.sEndBattleArmorBays)) {
return false;
}
- if (!isBetween(mek.getBattleArmorDoors(), f.sStartBattleArmorDoors, f.sEndBattleArmorDoors)) {
+ if (!StringUtil.isBetween(mek.getBattleArmorDoors(), f.sStartBattleArmorDoors, f.sEndBattleArmorDoors)) {
return false;
}
- if (!isBetween(mek.getBattleArmorUnits(), f.sStartBattleArmorUnits, f.sEndBattleArmorUnits)) {
+ if (!StringUtil.isBetween(mek.getBattleArmorUnits(), f.sStartBattleArmorUnits, f.sEndBattleArmorUnits)) {
return false;
}
- if (!isBetween(mek.getInfantryBays(), f.sStartInfantryBays, f.sEndInfantryBays)) {
+ if (!StringUtil.isBetween(mek.getInfantryBays(), f.sStartInfantryBays, f.sEndInfantryBays)) {
return false;
}
- if (!isBetween(mek.getInfantryDoors(), f.sStartInfantryDoors, f.sEndInfantryDoors)) {
+ if (!StringUtil.isBetween(mek.getInfantryDoors(), f.sStartInfantryDoors, f.sEndInfantryDoors)) {
return false;
}
- if (!isBetween(mek.getInfantryUnits(), f.sStartInfantryUnits, f.sEndInfantryUnits)) {
+ if (!StringUtil.isBetween(mek.getInfantryUnits(), f.sStartInfantryUnits, f.sEndInfantryUnits)) {
return false;
}
- if (!isBetween(mek.getSuperHeavyVehicleBays(), f.sStartSuperHeavyVehicleBays, f.sEndSuperHeavyVehicleBays)) {
+ if (!StringUtil.isBetween(mek.getSuperHeavyVehicleBays(), f.sStartSuperHeavyVehicleBays, f.sEndSuperHeavyVehicleBays)) {
return false;
}
- if (!isBetween(mek.getSuperHeavyVehicleDoors(), f.sStartSuperHeavyVehicleDoors, f.sEndSuperHeavyVehicleDoors)) {
+ if (!StringUtil.isBetween(mek.getSuperHeavyVehicleDoors(), f.sStartSuperHeavyVehicleDoors, f.sEndSuperHeavyVehicleDoors)) {
return false;
}
- if (!isBetween(mek.getSuperHeavyVehicleUnits(), f.sStartSuperHeavyVehicleUnits, f.sEndSuperHeavyVehicleUnits)) {
+ if (!StringUtil.isBetween(mek.getSuperHeavyVehicleUnits(), f.sStartSuperHeavyVehicleUnits, f.sEndSuperHeavyVehicleUnits)) {
return false;
}
- if (!isBetween(mek.getDropshuttleBays(), f.sStartDropshuttleBays, f.sEndDropshuttleBays)) {
+ if (!StringUtil.isBetween(mek.getDropshuttleBays(), f.sStartDropshuttleBays, f.sEndDropshuttleBays)) {
return false;
}
- if (!isBetween(mek.getDropshuttleDoors(), f.sStartDropshuttleDoors, f.sEndDropshuttleDoors)) {
+ if (!StringUtil.isBetween(mek.getDropshuttleDoors(), f.sStartDropshuttleDoors, f.sEndDropshuttleDoors)) {
return false;
}
- if (!isBetween(mek.getDropshuttelUnits(), f.sStartDropshuttleUnits, f.sEndDropshuttleUnits)) {
+ if (!StringUtil.isBetween(mek.getDropshuttelUnits(), f.sStartDropshuttleUnits, f.sEndDropshuttleUnits)) {
return false;
}
- if (!isBetween(mek.getDockingCollars(), f.sStartDockingCollars, f.sEndDockingCollars)) {
+ if (!StringUtil.isBetween(mek.getDockingCollars(), f.sStartDockingCollars, f.sEndDockingCollars)) {
return false;
}
- if (!isBetween(mek.getBattleArmorHandles(), f.sStartBattleArmorHandles, f.sEndBattleArmorHandles)) {
+ if (!StringUtil.isBetween(mek.getBattleArmorHandles(), f.sStartBattleArmorHandles, f.sEndBattleArmorHandles)) {
return false;
}
- if (!isBetween(mek.getCargoBayUnits(), f.sStartCargoBayUnits, f.sEndCargoBayUnits)) {
+ if (!StringUtil.isBetween(mek.getCargoBayUnits(), f.sStartCargoBayUnits, f.sEndCargoBayUnits)) {
return false;
}
- if (!isBetween(mek.getNavalRepairFacilities(), f.sStartNavalRepairFacilities, f.sEndNavalRepairFacilities)) {
+ if (!StringUtil.isBetween(mek.getNavalRepairFacilities(), f.sStartNavalRepairFacilities, f.sEndNavalRepairFacilities)) {
return false;
}
diff --git a/megamek/src/megamek/client/ui/swing/ClientGUI.java b/megamek/src/megamek/client/ui/swing/ClientGUI.java
index 4acc76028f5..937c6bd9dad 100644
--- a/megamek/src/megamek/client/ui/swing/ClientGUI.java
+++ b/megamek/src/megamek/client/ui/swing/ClientGUI.java
@@ -2069,6 +2069,99 @@ public void saveListFile(ArrayList unitList, String filename) {
}
}
+ /**
+ * Request MegaMekLab to print out record sheets for the current player's selected units.
+ * The method will try to find MML either automatically or based on a configured client setting.
+ *
+ * @param unitList The list of units to print
+ * @param button This should always be {@link ChatLounge#butPrintList}, if you need to trigger this method from somewhere else, override it.
+ */
+ public void printList(ArrayList unitList, JButton button) {
+ // Do nothing if there are no units to print
+ if ((unitList == null) || unitList.isEmpty()) {
+ return;
+ }
+
+ // Detect the MML executable.
+ // If the user hasn't set this manually, try to pick "MegaMakLab.exe"/".sh"
+ // from the same directory that MM is in
+ var mmlPath = CP.getMmlPath();
+ var autodetect = false;
+ if (null == mmlPath || mmlPath.isBlank()) {
+ autodetect = true;
+ if (System.getProperty("os.name").toLowerCase().contains("win")) {
+ mmlPath = "MegaMekLab.exe";
+ } else {
+ mmlPath = "MegaMekLab.sh";
+ }
+ }
+
+ var mml = new File(mmlPath);
+
+ if (!mml.canExecute()) {
+ if (autodetect) {
+ logger.error("Could not auto-detect MegaMekLab! Please configure the path to the MegaMekLab executable in the settings.", "Error printing unit list");
+ } else {
+ logger.error("%s does not appear to be an executable! Please configure the path to the MegaMekLab executable in the settings.".formatted(mml.getName()), "Error printing unit list");
+ }
+ return;
+ }
+
+ try {
+ // Save unit list to a temporary file
+ var unitFile = File.createTempFile("MegaMekPrint", ".mul");
+ EntityListFile.saveTo(unitFile, unitList);
+
+ String[] command;
+ if (mml.getName().toLowerCase().contains("gradle")) {
+ // If the executable is `gradlew`/`gradelw.bat`, assume it's the gradle wrapper
+ // which comes in the MML git repo. Compile and run MML from source in order to print units.
+ command = new String[] {
+ mml.getAbsolutePath(),
+ "run",
+ "--args=%s --no-startup".formatted(unitFile.getAbsolutePath())
+ };
+ } else {
+ // Start mml normally. "--no-startup" tells MML to exit after the user closes the
+ // print dialog (by printing or cancelling)
+ command = new String[] {
+ mml.getAbsolutePath(),
+ unitFile.getAbsolutePath(),
+ "--no-startup"
+ };
+ }
+ // It takes a while for MML to start, so we change the text of the button
+ // to let the user know that something is happening
+ button.setText(Messages.getString("ChatLounge.butPrintList.printing"));
+
+ logger.info("Running command: {}", String.join(" ", command));
+
+
+ var p = new ProcessBuilder(command)
+ .directory(mml.getAbsoluteFile().getParentFile())
+ .inheritIO()
+ .start();
+
+ // This thread's only purpose is to wait for the MML process to finish and change the button's text back to
+ // its original value.
+ new Thread(() -> {
+ try {
+ p.waitFor();
+ } catch (InterruptedException e) {
+ logger.error(e);
+ } finally {
+ button.setText(Messages.getString("ChatLounge.butPrintList"));
+ }
+ }).start();
+
+ } catch (Exception e) {
+ // If something goes wrong, probably ProcessBuild.start if anything,
+ // Make sure to set the button text back to what it started as no matter what.
+ logger.error(e, "Operation failed", "Error printing unit list");
+ button.setText(Messages.getString("ChatLounge.butPrintList"));
+ }
+ }
+
protected void saveVictoryList() {
String filename = client.getLocalPlayer().getName();
diff --git a/megamek/src/megamek/client/ui/swing/CollapseWarning.java b/megamek/src/megamek/client/ui/swing/CollapseWarning.java
index ac913fa6dab..05f26545d65 100644
--- a/megamek/src/megamek/client/ui/swing/CollapseWarning.java
+++ b/megamek/src/megamek/client/ui/swing/CollapseWarning.java
@@ -96,7 +96,7 @@ public static List findCFWarningsMovement(Game g, Entity e, Board b) {
List hexesToCheck = new ArrayList();
if (pos != null) {
- hexesToCheck = pos.allAtDistanceOrLess(range + 1);
+ hexesToCheck = pos.allAtDistanceOrLess(range);
} else {
return hexesToCheck;
}
diff --git a/megamek/src/megamek/client/ui/swing/CommonMenuBar.java b/megamek/src/megamek/client/ui/swing/CommonMenuBar.java
index a79418fee26..b992fc2d72b 100644
--- a/megamek/src/megamek/client/ui/swing/CommonMenuBar.java
+++ b/megamek/src/megamek/client/ui/swing/CommonMenuBar.java
@@ -356,11 +356,6 @@ public CommonMenuBar() {
toggleCFWarning.setToolTipText(Messages.getString("CommonMenuBar.viewToggleCFWarningToolTip"));
toggleCFWarning.setSelected(GUIP.getShowCFWarnings());
- /*
- * TODO: moveTraitor = createMenuItem(menu,
- * getString("CommonMenuBar.moveTraitor"), MovementDisplay.MOVE_TRAITOR);
- */
-
// Create the Help menu
menu = new JMenu(Messages.getString("CommonMenuBar.HelpMenu"));
menu.setMnemonic(VK_H);
diff --git a/megamek/src/megamek/client/ui/swing/CommonSettingsDialog.java b/megamek/src/megamek/client/ui/swing/CommonSettingsDialog.java
index 431ca7b313b..ae77658b6b6 100644
--- a/megamek/src/megamek/client/ui/swing/CommonSettingsDialog.java
+++ b/megamek/src/megamek/client/ui/swing/CommonSettingsDialog.java
@@ -212,6 +212,7 @@ private void moveElement(DefaultListModel srcModel, int srcIndex, int trg
private JTextField tfSoundMuteOthersFileName;
private JTextField userDir;
+ private JTextField mmlPath;
private final JCheckBox keepGameLog = new JCheckBox(Messages.getString("CommonSettingsDialog.keepGameLog"));
private JTextField gameLogFilename;
private final JCheckBox stampFilenames = new JCheckBox(Messages.getString("CommonSettingsDialog.stampFilenames"));
@@ -1724,6 +1725,23 @@ private JPanel getSettingsPanel() {
addLineSpacer(comps);
+ JLabel mmlPathLabel = new JLabel(Messages.getString("CommonSettingsDialog.mmlPath"));
+ mmlPathLabel.setToolTipText(Messages.getString("CommonSettingsDialog.mmlPath.tooltip"));
+ mmlPath = new JTextField(20);
+ mmlPath.setMaximumSize(new Dimension(250, 40));
+ mmlPath.setToolTipText(Messages.getString("CommonSettingsDialog.mmlPath.tooltip"));
+ JButton mmlPathChooser = new JButton("...");
+ mmlPathChooser.addActionListener(e ->
+ fileChoose(mmlPath, getFrame(), Messages.getString("CommonSettingsDialog.mmlPath.chooser.title"), false));
+ row = new ArrayList<>();
+ row.add(mmlPathLabel);
+ row.add(mmlPath);
+ row.add(Box.createHorizontalStrut(10));
+ row.add(mmlPathChooser);
+ comps.add(row);
+
+ addLineSpacer(comps);
+
// UI Theme
uiThemes = new JComboBox<>();
uiThemes.setMaximumSize(new Dimension(400, uiThemes.getMaximumSize().height));
@@ -1944,6 +1962,7 @@ public void setVisible(boolean visible) {
gameLogFilename.setEnabled(keepGameLog.isSelected());
gameLogFilename.setText(CP.getGameLogFilename());
userDir.setText(CP.getUserDir());
+ mmlPath.setText(CP.getMmlPath());
stampFilenames.setSelected(CP.stampFilenames());
stampFormat.setEnabled(stampFilenames.isSelected());
stampFormat.setText(CP.getStampFormat());
@@ -2421,6 +2440,7 @@ protected void okAction() {
CP.setKeepGameLog(keepGameLog.isSelected());
CP.setGameLogFilename(gameLogFilename.getText());
CP.setUserDir(userDir.getText());
+ CP.setMmlPath(mmlPath.getText());
CP.setStampFilenames(stampFilenames.isSelected());
CP.setStampFormat(stampFormat.getText());
CP.setReportKeywords(reportKeywordsTextPane.getText());
@@ -3452,13 +3472,19 @@ public static List filteredFilesWithSubDirs(File path, String fileEnding
* @param parent The parent JFrame of the settings dialog
*/
public static void fileChooseUserDir(JTextField userDirTextField, JFrame parent) {
- JFileChooser userDirChooser = new JFileChooser(userDirTextField.getText());
- userDirChooser.setDialogTitle(Messages.getString("CommonSettingsDialog.userDir.chooser.title"));
- userDirChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+ fileChoose(userDirTextField, parent, Messages.getString("CommonSettingsDialog.userDir.chooser.title"),true);
+ }
+
+ private static void fileChoose(JTextField textField, JFrame parent, String title, boolean directories) {
+ JFileChooser userDirChooser = new JFileChooser(textField.getText());
+ userDirChooser.setDialogTitle(title);
+ if (directories) {
+ userDirChooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);
+ }
int returnVal = userDirChooser.showOpenDialog(parent);
if ((returnVal == JFileChooser.APPROVE_OPTION) && (userDirChooser.getSelectedFile() != null)
- && userDirChooser.getSelectedFile().isDirectory()) {
- userDirTextField.setText(userDirChooser.getSelectedFile().toString());
+ && (directories ? userDirChooser.getSelectedFile().isDirectory() : userDirChooser.getSelectedFile().isFile())) {
+ textField.setText(userDirChooser.getSelectedFile().toString());
}
}
}
diff --git a/megamek/src/megamek/client/ui/swing/DeploymentDisplay.java b/megamek/src/megamek/client/ui/swing/DeploymentDisplay.java
index ef2c25e1e63..2fa58acd54e 100644
--- a/megamek/src/megamek/client/ui/swing/DeploymentDisplay.java
+++ b/megamek/src/megamek/client/ui/swing/DeploymentDisplay.java
@@ -131,6 +131,7 @@ public DeploymentDisplay(ClientGUI clientgui) {
butDone.setText("" + Messages.getString("DeploymentDisplay.Deploy") + "");
butDone.setEnabled(false);
+
setupButtonPanel();
}
diff --git a/megamek/src/megamek/client/ui/swing/EquipChoicePanel.java b/megamek/src/megamek/client/ui/swing/EquipChoicePanel.java
index 1126b1cb59e..dd75c80021d 100644
--- a/megamek/src/megamek/client/ui/swing/EquipChoicePanel.java
+++ b/megamek/src/megamek/client/ui/swing/EquipChoicePanel.java
@@ -1138,7 +1138,7 @@ public void applyChoice() {
if (chHotLoad.isSelected() != m_mounted.isHotLoaded()) {
m_mounted.setHotLoad(chHotLoad.isSelected());
// Set the mode too, so vehicles can switch back
- int numModes = m_mounted.getType().getModesCount();
+ int numModes = m_mounted.getModesCount();
for (int m = 0; m < numModes; m++) {
if (m_mounted.getType().getMode(m).getName()
.equals("HotLoad")) {
diff --git a/megamek/src/megamek/client/ui/swing/MapMenu.java b/megamek/src/megamek/client/ui/swing/MapMenu.java
index 3fad530711a..6bae3128378 100644
--- a/megamek/src/megamek/client/ui/swing/MapMenu.java
+++ b/megamek/src/megamek/client/ui/swing/MapMenu.java
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2005 - Ben Mazur (bmazur@sev.org)
- * Copyright (c) 2021-2022 - The MegaMek Team. All Rights Reserved.
+ * Copyright (c) 2021-2024 - The MegaMek Team. All Rights Reserved.
*
* This file is part of MegaMek.
*
@@ -24,22 +24,14 @@
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.math.BigInteger;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Objects;
-import java.util.StringTokenizer;
-import java.util.Vector;
-
-import javax.swing.JMenu;
-import javax.swing.JMenuItem;
-import javax.swing.JPopupMenu;
-import javax.swing.UIManager;
+import java.util.*;
+
+import javax.swing.*;
import megamek.client.Client;
import megamek.client.event.BoardViewEvent;
import megamek.client.ui.Messages;
+import megamek.client.ui.swing.gmCommands.GamemasterCommandPanel;
import megamek.client.ui.swing.lobby.LobbyUtility;
import megamek.common.*;
import megamek.common.Building.DemolitionCharge;
@@ -54,6 +46,7 @@
import megamek.common.weapons.other.CLFireExtinguisher;
import megamek.common.weapons.other.ISFireExtinguisher;
import megamek.logging.MMLogger;
+import megamek.server.commands.*;
/**
* Context menu for the board.
@@ -209,23 +202,6 @@ private boolean createMenu() {
}
}
-
- // Traitor Command
- JMenuItem item = new JMenuItem(Messages.getString("MovementDisplay.Traitor"));
- item.setActionCommand(MovementDisplay.MoveCommand.MOVE_TRAITOR.getCmd());
- item.addActionListener(evt -> {
- try {
- if (currentPanel instanceof MovementDisplay) {
- ((MovementDisplay) currentPanel).actionPerformed(evt);
- }
- } catch (Exception ex) {
- logger.error(ex, "");
- }
- });
-
- if (game.getPhase().isMovement()) {
- add(item);
- }
}
menu = touchOffExplosivesMenu();
@@ -239,7 +215,14 @@ private boolean createMenu() {
this.add(menu);
itemCount++;
}
+ menu = createPleaToRoyaltyMenu();
+ if (menu.getItemCount() > 0) {
+ this.addSeparator();
+ this.add(menu);
+ itemCount++;
+ }
+ this.addSeparator();
menu = createGamemasterMenu();
if (menu.getItemCount() > 0) {
this.add(menu);
@@ -253,17 +236,7 @@ private JMenuItem TargetMenuItem(Targetable t) {
JMenuItem item = new JMenuItem(Messages.getString("ClientGUI.targetMenuItem")
+ t.getDisplayName());
- String targetCode;
-
- if (t instanceof Entity) {
- targetCode = "E|" + ((Entity) t).getId();
- } else if (t instanceof BuildingTarget) {
- targetCode = "B|" + t.getPosition().getX() + "|" + t.getPosition().getY() + "|" + t.getTargetType();
- } else if (t instanceof MinefieldTarget) {
- targetCode = "M|" + t.getPosition().getX() + "|" + t.getPosition().getY();
- } else {
- targetCode = "H|" + t.getPosition().getX() + "|" + t.getPosition().getY() + "|" + t.getTargetType();
- }
+ String targetCode = getTargetCode(t);
item.setActionCommand(targetCode);
item.addActionListener(evt -> {
@@ -279,6 +252,21 @@ private JMenuItem TargetMenuItem(Targetable t) {
return item;
}
+ private static String getTargetCode(Targetable t) {
+ String targetCode;
+
+ if (t instanceof Entity) {
+ targetCode = "E|" + ((Entity) t).getId();
+ } else if (t instanceof BuildingTarget) {
+ targetCode = "B|" + t.getPosition().getX() + "|" + t.getPosition().getY() + "|" + t.getTargetType();
+ } else if (t instanceof MinefieldTarget) {
+ targetCode = "M|" + t.getPosition().getX() + "|" + t.getPosition().getY();
+ } else {
+ targetCode = "H|" + t.getPosition().getX() + "|" + t.getPosition().getY() + "|" + t.getTargetType();
+ }
+ return targetCode;
+ }
+
private @Nullable JMenuItem createChargeMenuItem() {
if (!client.getGame().getEntities(coords).hasNext()) {
return null;
@@ -420,6 +408,216 @@ private JMenu createSpecialHexDisplayMenu() {
return menu;
}
+ /**
+ * Creates various menus related to giving commands to allied bots
+ *
+ * @return JMenu
+ */
+ private JMenu createPleaToRoyaltyMenu() {
+ JMenu menu = new JMenu(Messages.getString("Bot.commands.title"));
+
+ for (var player : client.getGame().getPlayersList()) {
+ if (!player.isEnemyOf(client.getLocalPlayer()) && player.isBot()) {
+ menu.add(createBotCommands(player));
+ }
+ }
+ return menu;
+ }
+
+ private JMenu createBotCommands(Player bot) {
+ JMenu menu = new JMenu(bot.getName());
+
+ JMenu targetHexMenu = new JMenu(Messages.getString("Bot.commands.targetHex"));
+ JMenu prioritizeTargetUnitMenu = new JMenu(Messages.getString("Bot.commands.priority"));
+ JMenu ignoreTargetMenu= new JMenu(Messages.getString("Bot.commands.ignore"));
+
+ JMenu fleeMenu = new JMenu(Messages.getString("Bot.commands.flee"));
+ JMenu behaviorMenu = createBehaviorMenu(bot);
+
+
+ behaviorMenu.add(createBehaviorMenu(bot));
+ fleeMenu.add(createFleeMenu(bot));
+ targetHexMenu.add(createTargetHexMenuItem(bot));
+ menu.add(targetHexMenu);
+
+ for (Entity entity : client.getGame().getEntitiesVector(coords)) {
+ prioritizeTargetUnitMenu.add(createPrioritizeTargetUnitMenu(bot, entity));
+ ignoreTargetMenu.add(createIgnoreTargetUnitMenu(bot, entity));
+ }
+
+ if (prioritizeTargetUnitMenu.getItemCount() > 0) {
+ menu.add(prioritizeTargetUnitMenu);
+ }
+
+ if (ignoreTargetMenu.getItemCount() > 0) {
+ menu.add(ignoreTargetMenu);
+ }
+
+ menu.addSeparator();
+ menu.add(behaviorMenu);
+ menu.add(fleeMenu);
+ return menu;
+ }
+
+ JMenu createBehaviorMenu(Player bot) {
+ JMenu menu = new JMenu(Messages.getString("Bot.commands.behavior"));
+ menu.add(createCautionMenu(bot));
+ menu.add(createAvoidMenu(bot));
+ menu.add(createAggressionMenu(bot));
+ menu.add(createHerdingMenu(bot));
+ menu.add(createBraveryMenu(bot));
+ return menu;
+ }
+
+ JMenu createHerdingMenu(Player bot) {
+ JMenu menu = new JMenu(Messages.getString("Bot.commands.herding"));
+ JMenuItem item = new JMenuItem("+");
+ item.addActionListener(evt -> {
+ client.sendChat(String.format("%s: herd : +",
+ bot.getName()
+ ));
+ });
+ menu.add(item);
+ item = new JMenuItem("-");
+ item.addActionListener(evt -> {
+ client.sendChat(String.format("%s: herd : -",
+ bot.getName()
+ ));
+ });
+ menu.add(item);
+ return menu;
+ }
+
+ JMenu createBraveryMenu(Player bot) {
+ JMenu menu = new JMenu(Messages.getString("Bot.commands.bravery"));
+ JMenuItem item = new JMenuItem("+");
+ item.addActionListener(evt -> {
+ client.sendChat(String.format("%s: brave : +",
+ bot.getName()
+ ));
+ });
+ menu.add(item);
+ item = new JMenuItem("-");
+ item.addActionListener(evt -> {
+ client.sendChat(String.format("%s: brave : -",
+ bot.getName()
+ ));
+ });
+ menu.add(item);
+ return menu;
+ }
+
+ JMenu createAggressionMenu(Player bot) {
+ JMenu menu = new JMenu(Messages.getString("Bot.commands.aggression"));
+ JMenuItem item = new JMenuItem("+");
+ item.addActionListener(evt -> {
+ client.sendChat(String.format("%s: aggression : +",
+ bot.getName()
+ ));
+ });
+ menu.add(item);
+ item = new JMenuItem("-");
+ item.addActionListener(evt -> {
+ client.sendChat(String.format("%s: aggression : -",
+ bot.getName()
+ ));
+ });
+ menu.add(item);
+ return menu;
+ }
+
+ JMenu createAvoidMenu(Player bot) {
+ JMenu menu = new JMenu(Messages.getString("Bot.commands.avoid"));
+ JMenuItem item = new JMenuItem("+");
+ item.addActionListener(evt -> {
+ client.sendChat(String.format("%s: avoid : +",
+ bot.getName()
+ ));
+ });
+ menu.add(item);
+ item = new JMenuItem("-");
+ item.addActionListener(evt -> {
+ client.sendChat(String.format("%s: avoid : -",
+ bot.getName()
+ ));
+ });
+ menu.add(item);
+ return menu;
+ }
+
+ JMenu createCautionMenu(Player bot) {
+ JMenu menu = new JMenu(Messages.getString("Bot.commands.caution"));
+ JMenuItem item = new JMenuItem("+");
+ item.addActionListener(evt -> {
+ client.sendChat(String.format("%s: caution : +",
+ bot.getName()
+ ));
+ });
+ menu.add(item);
+ item = new JMenuItem("-");
+ item.addActionListener(evt -> {
+ client.sendChat(String.format("%s: caution : -",
+ bot.getName()
+ ));
+ });
+ menu.add(item);
+ return menu;
+ }
+
+
+ JMenuItem createFleeMenu(Player bot) {
+ JMenuItem item = new JMenuItem(Messages.getString("Bot.commands.flee.text"));
+ item.addActionListener(evt -> {
+ int confirm = JOptionPane.showConfirmDialog(
+ gui.getFrame(),
+ Messages.getString("Bot.commands.flee.confirmation", bot.getName()),
+ Messages.getString("Bot.commands.flee.confirm"),
+ JOptionPane.YES_NO_OPTION);
+
+ if (confirm == JOptionPane.YES_OPTION) {
+ client.sendChat(String.format("%s: flee",
+ bot.getName()
+ ));
+ }
+ }
+ );
+ return item;
+ }
+
+ JMenuItem createIgnoreTargetUnitMenu(Player bot, Entity entity) {
+ JMenuItem item = new JMenuItem(entity.getDisplayName());
+ item.addActionListener(evt ->
+ client.sendChat(String.format("%s: ignoreTarget : %d",
+ bot.getName(),
+ entity.getId()
+ ))
+ );
+ return item;
+ }
+
+ JMenuItem createPrioritizeTargetUnitMenu(Player bot, Entity entity) {
+ JMenuItem item = new JMenuItem(entity.getDisplayName());
+ item.addActionListener(evt ->
+ client.sendChat(String.format("%s: prioritize : %d",
+ bot.getName(),
+ entity.getId()
+ ))
+ );
+ return item;
+ }
+
+ JMenuItem createTargetHexMenuItem(Player bot) {
+ JMenuItem item = new JMenuItem(coords.toFriendlyString());
+ item.addActionListener(evt ->
+ client.sendChat(String.format("%s: target : %02d%02d",
+ bot.getName(),
+ coords.getX()+1,
+ coords.getY()+1
+ ))
+ );
+ return item;
+ }
+
/**
* Create various menus related to GameMaster (GM) mode
*
@@ -427,25 +625,70 @@ private JMenu createSpecialHexDisplayMenu() {
*/
private JMenu createGamemasterMenu() {
JMenu menu = new JMenu(Messages.getString("Gamemaster.Gamemaster"));
- if (!client.getLocalPlayer().getGameMaster()) {
- return menu;
- } else {
-
+ if (client.getLocalPlayer().getGameMaster()) {
JMenu dmgMenu = new JMenu(Messages.getString("Gamemaster.EditDamage"));
JMenu cfgMenu = new JMenu(Messages.getString("Gamemaster.Configure"));
+ JMenu traitorMenu = new JMenu(Messages.getString("Gamemaster.Traitor"));
+ JMenu rescueMenu = new JMenu(Messages.getString("Gamemaster.Rescue"));
+ JMenu killMenu = new JMenu(Messages.getString("Gamemaster.KillUnit"));
+ JMenu specialCommandsMenu = createGMSpecialCommandsMenu();
+
var entities = client.getGame().getEntitiesVector(coords);
+
for (Entity entity : entities) {
dmgMenu.add(createUnitEditorMenuItem(entity));
cfgMenu.add(createCustomMekMenuItem(entity));
+ traitorMenu.add(createTraitorMenuItem(entity));
+ rescueMenu.add(createRescueMenuItem(entity));
+ killMenu.add(createKillMenuItem(entity));
}
if (dmgMenu.getItemCount() != 0) {
menu.add(dmgMenu);
}
if (cfgMenu.getItemCount() != 0) {
menu.add(cfgMenu);
+ menu.addSeparator();
}
- return menu;
+ if (traitorMenu.getItemCount() != 0) {
+ menu.add(traitorMenu);
+ }
+ if (rescueMenu.getItemCount() != 0) {
+ menu.add(rescueMenu);
+ }
+ if (killMenu.getItemCount() != 0) {
+ menu.add(killMenu);
+ menu.addSeparator();
+ }
+ menu.add(specialCommandsMenu);
}
+ return menu;
+ }
+
+ /**
+ * Create a menu for special commands for the GM
+ * @return the menu
+ */
+ private JMenu createGMSpecialCommandsMenu() {
+ JMenu menu = new JMenu(Messages.getString("Gamemaster.SpecialCommands"));
+ List.of(
+ new ChangeOwnershipCommand(null, null),
+ new ChangeWeatherCommand(null, null),
+ new DisasterCommand(null, null),
+ new KillCommand(null, null),
+ new FirefightCommand(null, null),
+ new FirestarterCommand(null, null),
+ new FirestormCommand(null, null),
+ new NoFiresCommand(null, null),
+ new OrbitalBombardmentCommand(null, null),
+ new RemoveSmokeCommand(null, null),
+ new RescueCommand(null, null)
+ ).forEach(cmd -> {
+ JMenuItem item = new JMenuItem(cmd.getLongName());
+ item.addActionListener(evt -> new GamemasterCommandPanel(gui.getFrame(), gui, cmd, coords).setVisible(true));
+ menu.add(item);
+ });
+
+ return menu;
}
JMenuItem createCustomMekMenuItem(Entity entity) {
@@ -473,6 +716,112 @@ JMenuItem createUnitEditorMenuItem(Entity entity) {
return item;
}
+ /**
+ * Create traitor menu for game master options
+ * @param entity the entity to create the traitor menu for
+ * @return JMenu the traitor menu
+ */
+ private JMenuItem createTraitorMenuItem(Entity entity) {
+ // Traitor Command
+ JMenuItem item = new JMenuItem(Messages.getString("Gamemaster.Traitor.text", entity.getDisplayName()));
+ item.addActionListener(evt -> {
+ gui.getBoardView().setShouldIgnoreKeys(false);
+ var players = client.getGame().getPlayersList();
+ Integer[] playerIds = new Integer[players.size() - 1];
+ String[] playerNames = new String[players.size() - 1];
+ String[] options = new String[players.size() - 1];
+
+ Player currentOwner = entity.getOwner();
+ // Loop through the players vector and fill in the arrays
+ int idx = 0;
+ for (var player : players) {
+ if (player.getName().equals(currentOwner.getName())
+ || (player.getTeam() == Player.TEAM_UNASSIGNED)) {
+ continue;
+ }
+ playerIds[idx] = player.getId();
+ playerNames[idx] = player.getName();
+ options[idx] = player.getName() + " (ID: " + player.getId() + ")";
+ idx++;
+ }
+
+ // No players available?
+ if (idx == 0) {
+ JOptionPane.showMessageDialog(gui.getFrame(),
+ Messages.getString("Gamemaster.Traitor.text.noplayers"));
+ return;
+ }
+
+ // Dialog for choosing which player to transfer to
+ String option = (String) JOptionPane.showInputDialog(gui.getFrame(),
+ Messages.getString("Gamemaster.Traitor.text.selectplayer", entity.getDisplayName()),
+ Messages.getString("Gamemaster.Traitor.title"), JOptionPane.QUESTION_MESSAGE, null,
+ options, options[0]);
+
+ // Verify that we have a valid option...
+ if (option != null) {
+ // Now that we've selected a player, correctly associate the ID and name
+ int id = playerIds[Arrays.asList(options).indexOf(option)];
+ String name = playerNames[Arrays.asList(options).indexOf(option)];
+
+ // And now we perform the actual transfer
+ int confirm = JOptionPane.showConfirmDialog(
+ gui.getFrame(),
+ Messages.getString("Gamemaster.Traitor.confirmation", entity.getDisplayName(), name),
+ Messages.getString("Gamemaster.Traitor.confirm"),
+ JOptionPane.YES_NO_OPTION);
+
+ if (confirm == JOptionPane.YES_OPTION) {
+ client.sendChat(String.format("/changeOwner %d %d", entity.getId(), id));
+ }
+ }
+ });
+
+ return item;
+ }
+
+ /**
+ * Create a menu for killing a specific entity
+ *
+ * @param entity the entity to create the kill menu for
+ * @return JMenuItem the kill menu item
+ */
+ private JMenuItem createKillMenuItem(Entity entity) {
+ return createEntityCommandMenuItem(entity, "Gamemaster.KillUnit.text",
+ "Gamemaster.KillUnit.confirmation", String.format("/kill %d", entity.getId()));
+ }
+
+ /**
+ * Create a menu for rescuing a specific entity
+ * @param entity the entity to create the rescue menu for
+ * @return the rescue menu item
+ */
+ private JMenuItem createRescueMenuItem(Entity entity) {
+ return createEntityCommandMenuItem(entity, "Gamemaster.Rescue.text",
+ "Gamemaster.Rescue.confirmation", String.format("/rescue %d", entity.getId()));
+ }
+
+ /**
+ * Create a menu for a specific GM command
+ * @param entity the entity to create the menu for
+ * @param messageKey the menu item message key for the menu item
+ * @param confirmationKey the confirmation message key
+ * @param command the command that will be sent to the server
+ * @return the menu item
+ */
+ private JMenuItem createEntityCommandMenuItem(Entity entity, String messageKey, String confirmationKey, String command) {
+ JMenuItem item = new JMenuItem(Messages.getString(messageKey, entity.getDisplayName()));
+ item.addActionListener(evt -> {
+ int confirm = JOptionPane.showConfirmDialog(
+ gui.getFrame(), Messages.getString(confirmationKey, entity.getDisplayName()),
+ Messages.getString("Gamemaster.dialog.confirm"), JOptionPane.YES_NO_OPTION);
+ if (confirm == JOptionPane.YES_OPTION) {
+ client.sendChat(command);
+ }
+ });
+ return item;
+ }
+
private JMenu createSelectMenu() {
JMenu menu = new JMenu("Select");
// add select options
@@ -1421,9 +1770,7 @@ private void selectTarget() {
if (list.size() == 1) {
myTarget = selectedEntity = list.firstElement();
-
- if (currentPanel instanceof FiringDisplay) {
- FiringDisplay panel = (FiringDisplay) currentPanel;
+ if (currentPanel instanceof FiringDisplay panel) {
panel.target(myTarget);
} else if (currentPanel instanceof PhysicalDisplay) {
((PhysicalDisplay) currentPanel).target(myTarget);
diff --git a/megamek/src/megamek/client/ui/swing/MovementDisplay.java b/megamek/src/megamek/client/ui/swing/MovementDisplay.java
index 57b1492f687..47ef702b5f8 100644
--- a/megamek/src/megamek/client/ui/swing/MovementDisplay.java
+++ b/megamek/src/megamek/client/ui/swing/MovementDisplay.java
@@ -19,20 +19,6 @@
*/
package megamek.client.ui.swing;
-import static megamek.common.MiscType.F_CHAFF_POD;
-import static megamek.common.options.OptionsConstants.ADVGRNDMOV_TACOPS_ZIPLINES;
-
-import java.awt.Color;
-import java.awt.event.ActionEvent;
-import java.awt.event.InputEvent;
-import java.awt.event.MouseEvent;
-import java.util.*;
-import java.util.stream.Collectors;
-import java.util.stream.Stream;
-
-import javax.swing.JOptionPane;
-import javax.swing.SwingUtilities;
-
import megamek.client.event.BoardViewEvent;
import megamek.client.ui.Messages;
import megamek.client.ui.SharedUtility;
@@ -62,6 +48,19 @@
import megamek.common.preference.PreferenceManager;
import megamek.logging.MMLogger;
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.InputEvent;
+import java.awt.event.MouseEvent;
+import java.util.List;
+import java.util.*;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static megamek.common.MiscType.F_CHAFF_POD;
+import static megamek.common.options.OptionsConstants.ADVGRNDMOV_TACOPS_ZIPLINES;
+
public class MovementDisplay extends ActionPhaseDisplay {
private static final MMLogger logger = MMLogger.create(MovementDisplay.class);
@@ -230,7 +229,7 @@ public String toString() {
}
public String getHotKeyDesc() {
- String result = "";
+ String result;
String msgNext = Messages.getString("Next");
String msgPrevious = Messages.getString("Previous");
@@ -5297,11 +5296,15 @@ public synchronized void actionPerformed(ActionEvent ev) {
String[] playerNames = new String[players.size() - 1];
String[] options = new String[players.size() - 1];
Entity e = ce();
+ if (e == null) {
+ return;
+ }
+ Player currentOwner = e.getOwner();
// Loop through the players vector and fill in the arrays
int idx = 0;
for (var player : players) {
- if (player.getName().equals(clientgui.getClient().getLocalPlayer().getName())
+ if (player.getName().equals(currentOwner.getName())
|| (player.getTeam() == Player.TEAM_UNASSIGNED)) {
continue;
}
@@ -5321,7 +5324,7 @@ public synchronized void actionPerformed(ActionEvent ev) {
// Dialog for choosing which player to transfer to
String option = (String) JOptionPane.showInputDialog(clientgui.getFrame(),
- "Choose the player to gain ownership of this unit when it turns traitor",
+ "Choose the player to gain ownership of this unit (" + e.getDisplayName() + ") when it turns traitor",
"Traitor", JOptionPane.QUESTION_MESSAGE, null,
options, options[0]);
diff --git a/megamek/src/megamek/client/ui/swing/StatusBarPhaseDisplay.java b/megamek/src/megamek/client/ui/swing/StatusBarPhaseDisplay.java
index 1181c81be5a..a0590743f68 100644
--- a/megamek/src/megamek/client/ui/swing/StatusBarPhaseDisplay.java
+++ b/megamek/src/megamek/client/ui/swing/StatusBarPhaseDisplay.java
@@ -45,6 +45,7 @@
import javax.swing.ToolTipManager;
import javax.swing.border.EmptyBorder;
+import megamek.client.AbstractClient;
import megamek.client.ui.GBC;
import megamek.client.ui.Messages;
import megamek.client.ui.swing.util.KeyBindReceiver;
@@ -157,8 +158,25 @@ public void actionPerformed(ActionEvent e) {
KeyBindParser.addPreferenceChangeListener(this);
MegaMekGUI.getKeyDispatcher().registerCommandAction(KeyCommandBind.EXTEND_TURN_TIMER, this, this::extendTimer);
+ MegaMekGUI.getKeyDispatcher().registerCommandAction(KeyCommandBind.PAUSE.cmd, this::pauseGameWhenOnlyBotUnitsRemain);
+ MegaMekGUI.getKeyDispatcher().registerCommandAction(KeyCommandBind.UNPAUSE.cmd,
+ () -> ((AbstractClient) clientgui.getClient()).sendUnpause());
}
+ private void pauseGameWhenOnlyBotUnitsRemain() {
+ if (isIgnoringEvents() || !isVisible()) {
+ return;
+ }
+ IGame game = getClientgui().getClient().getGame();
+ List nonBots = game.getPlayersList().stream().filter(p -> !p.isBot()).toList();
+ boolean liveUnitsRemaining = nonBots.stream().anyMatch(p -> game.getEntitiesOwnedBy(p) > 0);
+ if (liveUnitsRemaining) {
+ clientgui.getClient().sendChat("Pausing the game only works when only bot units remain.");
+ } else {
+ clientgui.getClient().sendChat("Requesting game pause.");
+ ((AbstractClient) clientgui.getClient()).sendPause();
+ }
+ }
/** Returns the list of buttons that should be displayed. */
protected abstract List getButtonList();
diff --git a/megamek/src/megamek/client/ui/swing/boardview/BoardView.java b/megamek/src/megamek/client/ui/swing/boardview/BoardView.java
index 1a5d79f083a..f59f1134346 100644
--- a/megamek/src/megamek/client/ui/swing/boardview/BoardView.java
+++ b/megamek/src/megamek/client/ui/swing/boardview/BoardView.java
@@ -87,6 +87,7 @@
import megamek.common.util.ImageUtil;
import megamek.common.util.fileUtils.MegaMekFile;
import megamek.logging.MMLogger;
+import megamek.server.props.OrbitalBombardment;
/**
* Displays the board; lets the user scroll around and select points on it.
@@ -637,6 +638,10 @@ public void mouseDragged(MouseEvent e) {
SpecialHexDisplay.Type.BOMB_HIT.init();
SpecialHexDisplay.Type.BOMB_DRIFT.init();
SpecialHexDisplay.Type.PLAYER_NOTE.init();
+ SpecialHexDisplay.Type.ORBITAL_BOMBARDMENT.init();
+ SpecialHexDisplay.Type.ORBITAL_BOMBARDMENT_INCOMING.init();
+ SpecialHexDisplay.Type.NUKE_HIT.init();
+ SpecialHexDisplay.Type.NUKE_INCOMING.init();
fovHighlightingAndDarkening = new FovHighlightingAndDarkening(this);
@@ -1454,6 +1459,44 @@ private Mounted> selectedWeapon() {
return (clientgui != null) ? clientgui.getDisplayedWeapon().orElse(null) : null;
}
+ /**
+ * Draw the orbital bombardment attacks on the board view
+ *
+ * @author Luana Coppio
+ * @param boardGraphics The graphics object to draw on
+ */
+ private void drawOrbitalBombardmentHexes(Graphics boardGraphics) {
+ Image orbitalBombardmentImage = tileManager.getOrbitalBombardmentImage();
+ Rectangle view = boardGraphics.getClipBounds();
+
+ // Compute the origin of the viewing area
+ int drawX = (view.x / (int) (HEX_WC * scale)) - 1;
+ int drawY = (view.y / (int) (HEX_H * scale)) - 1;
+
+ // Compute size of viewing area
+ int drawWidth = (view.width / (int) (HEX_WC * scale)) + 3;
+ int drawHeight = (view.height / (int) (HEX_H * scale)) + 3;
+
+ // Draw incoming artillery sprites - requires server to update client's
+ // view of game
+ for (Enumeration attacks = game.getOrbitalBombardmentAttacks(); attacks.hasMoreElements();) {
+ final OrbitalBombardment orbitalBombardment = attacks.nextElement();
+ final Coords c = new Coords(orbitalBombardment.getX(), orbitalBombardment.getY());
+ // Is the Coord within the viewing area?
+ boolean insideViewArea = ((c.getX() >= drawX) && (c.getX() <= (drawX + drawWidth))
+ && (c.getY() >= drawY) && (c.getY() <= (drawY + drawHeight)));
+ if (insideViewArea) {
+ Point p = getHexLocation(c);
+ boardGraphics.drawImage(getScaledImage(orbitalBombardmentImage, true), p.x, p.y, boardPanel);
+ for (Coords c2 : c.allAtDistanceOrLess(orbitalBombardment.getRadius())) {
+ Point p2 = getHexLocation(c2);
+ boardGraphics.drawImage(getScaledImage(orbitalBombardmentImage, true), p2.x, p2.y, boardPanel);
+ }
+ }
+
+ }
+ }
+
/**
* Display artillery modifier in pretargeted hexes
*/
@@ -1639,6 +1682,9 @@ public BufferedImage getEntireBoardImage(boolean ignoreUnits, boolean useBaseZoo
// Artillery targets
drawArtilleryHexes(boardGraph);
+ // draw Orbital Bombardment targets;
+ drawOrbitalBombardmentHexes(boardGraph);
+
// draw highlight border
drawSprite(boardGraph, highlightSprite);
@@ -1797,6 +1843,9 @@ private void drawHex(Coords c, Graphics boardGraph, boolean saveBoardImage) {
}
final Hex hex = game.getBoard().getHex(c);
+ if (hex == null) {
+ return;
+ }
final Point hexLoc = getHexLocation(c);
PlanetaryConditions conditions = game.getPlanetaryConditions();
@@ -2078,7 +2127,7 @@ private void drawHex(Coords c, Graphics boardGraph, boolean saveBoardImage) {
if (shdList != null) {
for (SpecialHexDisplay shd : shdList) {
if (shd.drawNow(game.getPhase(), game.getRoundCount(), localPlayer, GUIP)) {
- scaledImage = getScaledImage(shd.getType().getDefaultImage(), true);
+ scaledImage = getScaledImage(shd.getDefaultImage(), true);
g.drawImage(scaledImage, 0, 0, boardPanel);
}
}
diff --git a/megamek/src/megamek/client/ui/swing/boardview/TerrainShadowHelper.java b/megamek/src/megamek/client/ui/swing/boardview/TerrainShadowHelper.java
index 9b04715eb81..2de4ebb2d26 100644
--- a/megamek/src/megamek/client/ui/swing/boardview/TerrainShadowHelper.java
+++ b/megamek/src/megamek/client/ui/swing/boardview/TerrainShadowHelper.java
@@ -185,9 +185,11 @@ BufferedImage updateShadowMap() {
surrounded = false;
} else {
Hex nhex = board.getHex(c.translated(dir));
- int lv = nhex.getLevel();
- if (lv < level) {
- surrounded = false;
+ if (nhex != null) {
+ int lv = nhex.getLevel();
+ if (lv < level) {
+ surrounded = false;
+ }
}
}
}
diff --git a/megamek/src/megamek/client/ui/swing/gmCommands/GamemasterCommandPanel.java b/megamek/src/megamek/client/ui/swing/gmCommands/GamemasterCommandPanel.java
new file mode 100644
index 00000000000..d2e4d7fa70a
--- /dev/null
+++ b/megamek/src/megamek/client/ui/swing/gmCommands/GamemasterCommandPanel.java
@@ -0,0 +1,205 @@
+package megamek.client.ui.swing.gmCommands;
+
+import megamek.client.ui.swing.ClientGUI;
+import megamek.common.Coords;
+import megamek.common.annotations.Nullable;
+import megamek.server.commands.GamemasterServerCommand;
+import megamek.server.commands.arguments.Argument;
+import megamek.server.commands.arguments.EnumArgument;
+import megamek.server.commands.arguments.IntegerArgument;
+import megamek.server.commands.arguments.OptionalEnumArgument;
+
+import javax.swing.*;
+import java.awt.*;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * Dialog for executing a gamemaster command.
+ */
+public class GamemasterCommandPanel extends JDialog {
+ private final GamemasterServerCommand command;
+ private final ClientGUI client;
+ private final Coords coords;
+
+ /**
+ * Constructor for the dialog for executing a gamemaster command.
+ *
+ * @param parent The parent frame.
+ * @param client The client GUI.
+ * @param command The command to render.
+ */
+ public GamemasterCommandPanel(JFrame parent, ClientGUI client, GamemasterServerCommand command, @Nullable Coords coords) {
+ super(parent, command.getName(), true);
+ this.command = command;
+ this.client = client;
+ this.coords = coords;
+ initializeUI(parent);
+ }
+
+ private void initializeUI(JFrame parent) {
+ setLayout(new BoxLayout(getContentPane(), BoxLayout.Y_AXIS));
+ addTitleAndDescription();
+ Map argumentComponents = addArgumentComponents();
+ addExecuteButton(argumentComponents);
+ pack();
+ setLocationRelativeTo(parent);
+ }
+
+ private void addTitleAndDescription() {
+ JPanel titlePanel = new JPanel();
+ titlePanel.setLayout(new BoxLayout(titlePanel, BoxLayout.Y_AXIS));
+
+ JLabel titleLabel = new JLabel(command.getLongName());
+ titleLabel.setFont(new Font("Arial", Font.BOLD, 16));
+ titlePanel.add(titleLabel);
+
+ JLabel helpLabel = new JLabel(command.getHelpHtml());
+ helpLabel.setFont(new Font("Arial", Font.PLAIN, 12));
+ titlePanel.add(helpLabel);
+
+ add(titlePanel);
+ }
+
+ private Map addArgumentComponents() {
+ List> arguments = command.defineArguments();
+ Map argumentComponents = new HashMap<>();
+
+ for (Argument> argument : arguments) {
+ JPanel argumentPanel = createArgumentPanel(argument);
+ add(argumentPanel);
+ argumentComponents.put(argument.getName(), getArgumentComponent(argument, argumentPanel));
+ }
+ return argumentComponents;
+ }
+
+ private JPanel createArgumentPanel(Argument> argument) {
+ JPanel argumentPanel = new JPanel();
+ argumentPanel.setLayout(new FlowLayout());
+ JLabel label = new JLabel(argument.getName() + ":");
+ argumentPanel.add(label);
+ return argumentPanel;
+ }
+
+ private JComponent getArgumentComponent(Argument> argument, JPanel argumentPanel) {
+ if (argument instanceof IntegerArgument intArg) {
+ JSpinner spinner = createSpinner(intArg);
+ argumentPanel.add(spinner);
+ return spinner;
+ } else if (argument instanceof OptionalEnumArgument> enumArg) {
+ JComboBox comboBox = createOptionalEnumComboBox(enumArg);
+ argumentPanel.add(comboBox);
+ return comboBox;
+ } else if (argument instanceof EnumArgument> enumArg) {
+ JComboBox comboBox = createEnumComboBox(enumArg);
+ argumentPanel.add(comboBox);
+ return comboBox;
+ }
+ return null;
+ }
+
+ private boolean isArgumentX(Argument> argument) {
+ return argument.getName().equals("x");
+ }
+
+ private boolean isArgumentY(Argument> argument) {
+ return argument.getName().equals("y");
+ }
+
+ private int getIntArgumentDefaultValue(IntegerArgument intArg) {
+ return intArg.hasDefaultValue() ? intArg.getValue() : isArgumentX(intArg) ? coords.getX()+1 :
+ isArgumentY(intArg) ? coords.getY()+1 : 0;
+ }
+
+ private JSpinner createSpinner(IntegerArgument intArg) {
+ return new JSpinner(new SpinnerNumberModel(
+ getIntArgumentDefaultValue(intArg),
+ intArg.getMinValue(),
+ intArg.getMaxValue(),
+ 1));
+ }
+
+ private JComboBox createOptionalEnumComboBox(OptionalEnumArgument> enumArg) {
+ JComboBox comboBox = new JComboBox<>();
+ if (enumArg.getValue() == null) {
+ comboBox.addItem("-");
+ comboBox.setSelectedItem("-");
+ }
+ for (var arg : enumArg.getEnumType().getEnumConstants()) {
+ comboBox.addItem(arg.ordinal() + ": " + arg);
+ }
+ if (enumArg.getValue() != null) {
+ comboBox.setSelectedItem(enumArg.getValue().ordinal() + ": " + enumArg.getValue().toString());
+ }
+ return comboBox;
+ }
+
+ private JComboBox createEnumComboBox(EnumArgument> enumArg) {
+ JComboBox comboBox = new JComboBox<>();
+ for (Enum> constant : enumArg.getEnumType().getEnumConstants()) {
+ comboBox.addItem(constant.name());
+ }
+ if (enumArg.getValue() != null) {
+ comboBox.setSelectedItem(enumArg.getValue().name());
+ }
+ return comboBox;
+ }
+
+ private void addExecuteButton(Map argumentComponents) {
+ add(getExecuteButton(argumentComponents));
+ }
+
+ private JButton getExecuteButton(Map argumentComponents) {
+ JButton executeButton = new JButton("Execute Command");
+ executeButton.addActionListener(e -> {
+ int response = JOptionPane.showConfirmDialog(
+ this,
+ "Are you sure you want to execute this command?",
+ "Execute Command",
+ JOptionPane.YES_NO_OPTION
+ );
+ if (response == JOptionPane.YES_OPTION) {
+ executeCommand(argumentComponents);
+ }
+ });
+ return executeButton;
+ }
+
+ /**
+ * Execute the command with the given arguments.
+ * It runs the command using the client chat, this way the command is sent to the server.
+ * All arguments are loaded as named variables in the form of "argumentName=argumentValue".
+ *
+ * @param argumentComponents The components that hold the arguments selected.
+ */
+ private void executeCommand(Map argumentComponents) {
+ List> arguments = command.defineArguments();
+ String[] args = new String[arguments.size()];
+
+ for (int i = 0; i < arguments.size(); i++) {
+ Argument> argument = arguments.get(i);
+ JComponent component = argumentComponents.get(argument.getName());
+
+ if (component instanceof JSpinner) {
+ args[i] = argument.getName() + "=" + ((JSpinner) component).getValue().toString();
+ } else if (component instanceof JComboBox) {
+ if (argument instanceof OptionalEnumArgument>) {
+ String selectedItem = (String) ((JComboBox>) component).getSelectedItem();
+ if (selectedItem == null || selectedItem.equals("-")) {
+ // If it is null we just set it to an empty string and move on
+ args[i] = "";
+ continue;
+ }
+ var selectedItemValue = selectedItem.split(":")[0].trim();
+ args[i] = argument.getName() + "=" + selectedItemValue;
+ } else {
+ args[i] = argument.getName() + "=" + Objects.requireNonNull(((JComboBox>) component).getSelectedItem());
+ }
+ }
+ }
+
+ client.getClient().sendChat("/" + command.getName() + " " + String.join(" ", args));
+ }
+}
diff --git a/megamek/src/megamek/client/ui/swing/lobby/ChatLounge.java b/megamek/src/megamek/client/ui/swing/lobby/ChatLounge.java
index 2d8b7e667ff..985774d18e8 100644
--- a/megamek/src/megamek/client/ui/swing/lobby/ChatLounge.java
+++ b/megamek/src/megamek/client/ui/swing/lobby/ChatLounge.java
@@ -80,6 +80,7 @@
import megamek.client.generator.RandomCallsignGenerator;
import megamek.client.generator.RandomNameGenerator;
import megamek.client.ui.Messages;
+import megamek.client.ui.advancedSearchMap.AdvancedSearchMapDialog;
import megamek.client.ui.dialogs.BotConfigDialog;
import megamek.client.ui.dialogs.CamoChooserDialog;
import megamek.client.ui.enums.DialogResult;
@@ -160,6 +161,7 @@ public class ChatLounge extends AbstractPhaseDisplay implements
private JButton butNames = new JButton(Messages.getString("ChatLounge.butNames"));
private JButton butLoadList = new JButton(Messages.getString("ChatLounge.butLoadList"));
private JButton butSaveList = new JButton(Messages.getString("ChatLounge.butSaveList"));
+ private JButton butPrintList = new JButton(Messages.getString("ChatLounge.butPrintList"));
/* Unit Table */
private MekTable mekTable;
@@ -213,6 +215,7 @@ public class ChatLounge extends AbstractPhaseDisplay implements
private JLabel lblBoardSize = new JLabel(Messages.getString("ChatLounge.labBoardSize"));
private JButton butHelp = new JButton(" " + Messages.getString("ChatLounge.butHelp") + " ");
+ private JButton butAdvancedSearchMap = new JButton(Messages.getString("AdvancedSearchMapDialog.title"));
private JButton butConditions = new JButton(Messages.getString("ChatLounge.butConditions"));
private JButton butRandomMap = new JButton(Messages.getString("BoardSelectionDialog.GeneratedMapSettings"));
@@ -276,6 +279,7 @@ public class ChatLounge extends AbstractPhaseDisplay implements
private static final String CL_ACTIONCOMMAND_LOADLIST = "load_list";
private static final String CL_ACTIONCOMMAND_SAVELIST = "save_list";
+ private static final String CL_ACTIONCOMMAND_PRINTLIST = "print_list";
private static final String CL_ACTIONCOMMAND_LOADMEK = "load_mek";
private static final String CL_ACTIONCOMMAND_ADDBOT = "add_bot";
private static final String CL_ACTIONCOMMAND_REMOVEBOT = "remove_bot";
@@ -365,6 +369,7 @@ private void setupListeners() {
butRandomMap.addActionListener(lobbyListener);
butRemoveBot.addActionListener(lobbyListener);
butSaveList.addActionListener(lobbyListener);
+ butPrintList.addActionListener(lobbyListener);
butShowUnitID.addActionListener(lobbyListener);
butSkills.addActionListener(lobbyListener);
butSpaceSize.addActionListener(lobbyListener);
@@ -384,6 +389,7 @@ private void setupListeners() {
butDetach.addActionListener(lobbyListener);
butCancelSearch.addActionListener(lobbyListener);
butHelp.addActionListener(lobbyListener);
+ butAdvancedSearchMap.addActionListener(lobbyListener);
butListView.addActionListener(lobbyListener);
butForceView.addActionListener(lobbyListener);
butCollapse.addActionListener(lobbyListener);
@@ -546,6 +552,9 @@ private void setupUnitConfig() {
butLoadList.setEnabled(mscLoaded);
butSaveList.setActionCommand(CL_ACTIONCOMMAND_SAVELIST);
butSaveList.setEnabled(false);
+ butPrintList.setActionCommand(CL_ACTIONCOMMAND_PRINTLIST);
+ butPrintList.setEnabled(false);
+ butPrintList.setToolTipText(Messages.getString("ChatLounge.butPrintList.tooltip"));
butAdd.setEnabled(mscLoaded);
butAdd.setActionCommand(CL_ACTIONCOMMAND_LOADMEK);
butArmy.setEnabled(mscLoaded);
@@ -561,6 +570,7 @@ private void setupUnitConfig() {
panUnitInfoGrid.add(butLoadList);
panUnitInfoGrid.add(butSaveList);
panUnitInfoGrid.add(butNames);
+ panUnitInfoGrid.add(butPrintList);
panUnitInfo.add(panUnitInfoAdd);
panUnitInfo.add(panUnitInfoGrid);
@@ -679,6 +689,7 @@ private void setupMapPanel() {
JPanel panHelp = new JPanel(new GridLayout(1, 1));
panHelp.add(butHelp);
+ panHelp.add(butAdvancedSearchMap);
FixedYPanel panTopRowsHelp = new FixedYPanel(new FlowLayout(FlowLayout.CENTER, 30, 5));
panTopRowsHelp.add(panTopRows);
@@ -971,6 +982,7 @@ private void refreshMapUI() {
butLoadMapSetup.setEnabled(!inSpace);
butMapShrinkW.setEnabled(mapSettings.getMapWidth() > 1);
butMapShrinkH.setEnabled(mapSettings.getMapHeight() > 1);
+ butAdvancedSearchMap.setEnabled(!inSpace && (mapSettings.getMapWidth() == 1) && (mapSettings.getMapHeight() == 1));
butGroundMap.removeActionListener(lobbyListener);
butLowAtmoMap.removeActionListener(lobbyListener);
@@ -1739,7 +1751,7 @@ public void actionPerformed(ActionEvent ev) {
}
clientgui.loadListFile(c.getLocalPlayer());
- } else if (ev.getSource().equals(butSaveList)) {
+ } else if (ev.getSource().equals(butSaveList) || ev.getSource().equals(butPrintList)) {
// Allow the player to save their current
// list of entities to a file.
Client c = getSelectedClient();
@@ -1752,7 +1764,11 @@ public void actionPerformed(ActionEvent ev) {
for (Entity entity : entities) {
entity.setForceString(game().getForces().forceStringFor(entity));
}
- clientgui.saveListFile(entities, c.getLocalPlayer().getName());
+ if (ev.getSource().equals(butSaveList)) {
+ clientgui.saveListFile(entities, c.getLocalPlayer().getName());
+ } else {
+ clientgui.printList(entities, (JButton) ev.getSource());
+ }
} else if (ev.getSource().equals(butAddBot)) {
configAndCreateBot(null);
@@ -1897,6 +1913,35 @@ public void actionPerformed(ActionEvent ev) {
dialog.pack();
dialog.setVisible(true);
+ } else if (ev.getSource() == butAdvancedSearchMap) {
+ setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
+ AdvancedSearchMapDialog asmd = new AdvancedSearchMapDialog(clientgui.getFrame());
+ setCursor(Cursor.getDefaultCursor());
+
+ if (asmd.showDialog().isConfirmed()) {
+ String path = asmd.getPath();
+ if (path != null) {
+ Board board = new Board(16, 17);
+ board.load(new MegaMekFile(Configuration.boardsDir(), path).getFile());
+ String boardName = path.replace(".board", "");
+ boardName = boardName.replace("\\", "/");
+ mapSettings.getBoardsSelectedVector().clear();
+ mapSettings.setMapSize(1, 1);
+ mapSettings.setBoardSize(board.getWidth(), board.getHeight());
+ clientgui.getClient().sendMapDimensions(mapSettings);
+ mapSettings.getBoardsSelectedVector().set(0, boardName);
+ refreshMapUI();
+ clientgui.getClient().sendMapSettings(mapSettings);
+
+ if (boardPreviewW.isVisible()) {
+ previewGameBoard();
+ }
+
+ String msg = clientgui.getClient().getLocalPlayer() + " changed map to: " + boardName;
+ clientgui.getClient().sendServerChat(Player.PLAYER_NONE, msg);
+ }
+ }
+
} else if (ev.getSource() == butListView) {
scrMekTable.setViewportView(mekTable);
butCollapse.setEnabled(false);
@@ -2286,6 +2331,7 @@ public void removeAllListeners() {
butRandomMap.removeActionListener(lobbyListener);
butRemoveBot.removeActionListener(lobbyListener);
butSaveList.removeActionListener(lobbyListener);
+ butPrintList.removeActionListener(lobbyListener);
butShowUnitID.removeActionListener(lobbyListener);
butSkills.removeActionListener(lobbyListener);
butSpaceSize.removeActionListener(lobbyListener);
@@ -2405,10 +2451,12 @@ private void refreshPlayerConfig() {
// Disable the Remove Bot button for the "player" of a "Connect As Bot" client
butRemoveBot.setEnabled(isSingleLocalBot);
butSaveList.setEnabled(false);
+ butPrintList.setEnabled(false);
if (isSinglePlayer) {
var selPlayer = theElement(selPlayers);
var hasUnits = !game().getPlayerEntities(selPlayer, false).isEmpty();
butSaveList.setEnabled(hasUnits && unitsVisible(selPlayer));
+ butPrintList.setEnabled(hasUnits && unitsVisible(selPlayer));
setTeamSelectedItem(selPlayer.getTeam());
}
}
diff --git a/megamek/src/megamek/client/ui/swing/minimap/Minimap.java b/megamek/src/megamek/client/ui/swing/minimap/Minimap.java
index 7aa0f11da92..10df3f5a6ba 100644
--- a/megamek/src/megamek/client/ui/swing/minimap/Minimap.java
+++ b/megamek/src/megamek/client/ui/swing/minimap/Minimap.java
@@ -536,6 +536,9 @@ private void drawMap(boolean forceDraw) {
for (int j = 0; j < board.getWidth(); j++) {
for (int k = 0; k < board.getHeight(); k++) {
Hex h = board.getHex(j, k);
+ if (h == null) {
+ continue;
+ }
if (dirtyMap || dirty[j / 10][k / 10]) {
gg.setColor(terrainColor(h));
if (h.containsTerrain(SPACE)) {
diff --git a/megamek/src/megamek/client/ui/swing/tileset/TilesetManager.java b/megamek/src/megamek/client/ui/swing/tileset/TilesetManager.java
index b051ee2b2e9..81fd4885726 100644
--- a/megamek/src/megamek/client/ui/swing/tileset/TilesetManager.java
+++ b/megamek/src/megamek/client/ui/swing/tileset/TilesetManager.java
@@ -76,6 +76,9 @@ public class TilesetManager implements IPreferenceChangeListener {
private static final String FILENAME_ARTILLERY_AUTOHIT_IMAGE = "artyauto.gif";
private static final String FILENAME_ARTILLERY_ADJUSTED_IMAGE = "artyadj.gif";
private static final String FILENAME_ARTILLERY_INCOMING_IMAGE = "artyinc.gif";
+ private static final String FILENAME_ARTILLERY_HIT_IMAGE = "artyhit.gif";
+
+ public static final String FILENAME_ORBITAL_BOMBARDMENT_INCOMING_IMAGE = "artyinc.gif";
public static final int ARTILLERY_AUTOHIT = 0;
public static final int ARTILLERY_ADJUSTED = 1;
@@ -106,6 +109,8 @@ public class TilesetManager implements IPreferenceChangeListener {
private Image artilleryAutohit;
private Image artilleryAdjusted;
private Image artilleryIncoming;
+ private Image orbitalBombardmentIncoming;
+ private Image orbitalBombardmentHit;
/**
* Hexes under the effects of ECM have a shaded "static" image displayed,
@@ -414,6 +419,10 @@ public Image getEcmStaticImage(Color tint) {
return image;
}
+ public Image getOrbitalBombardmentImage() {
+ return orbitalBombardmentIncoming;
+ }
+
public Image getArtilleryTarget(int which) {
switch (which) {
case ARTILLERY_AUTOHIT:
@@ -486,6 +495,8 @@ public void loadNeededImages(Game game) {
artilleryAutohit = LoadSpecificImage(Configuration.hexesDir(), FILENAME_ARTILLERY_AUTOHIT_IMAGE);
artilleryAdjusted = LoadSpecificImage(Configuration.hexesDir(), FILENAME_ARTILLERY_ADJUSTED_IMAGE);
artilleryIncoming = LoadSpecificImage(Configuration.hexesDir(), FILENAME_ARTILLERY_INCOMING_IMAGE);
+ orbitalBombardmentIncoming = LoadSpecificImage(Configuration.hexesDir(), FILENAME_ORBITAL_BOMBARDMENT_INCOMING_IMAGE);
+ orbitalBombardmentHit = LoadSpecificImage(Configuration.hexesDir(), FILENAME_ARTILLERY_HIT_IMAGE);
started = true;
}
diff --git a/megamek/src/megamek/client/ui/swing/unitDisplay/WeaponPanel.java b/megamek/src/megamek/client/ui/swing/unitDisplay/WeaponPanel.java
index 072cf3035e3..db9153b3e45 100644
--- a/megamek/src/megamek/client/ui/swing/unitDisplay/WeaponPanel.java
+++ b/megamek/src/megamek/client/ui/swing/unitDisplay/WeaponPanel.java
@@ -225,13 +225,18 @@ public WeaponMounted getWeaponAt(int index) {
@Override
public String getElementAt(int index) {
final WeaponMounted mounted = weapons.get(index);
- final WeaponType wtype = (WeaponType) mounted.getType();
+ final WeaponType wtype = mounted.getType();
Game game = null;
if (unitDisplay.getClientGUI() != null) {
game = unitDisplay.getClientGUI().getClient().getGame();
}
StringBuilder wn = new StringBuilder(mounted.getDesc());
+ if ((mounted.getLinkedBy() != null)
+ && (mounted.getLinkedBy().getType() instanceof MiscType)
+ && (mounted.getLinkedBy().getType().hasFlag(MiscType.F_RISC_LASER_PULSE_MODULE))) {
+ wn.append("+").append(mounted.getLinkedBy().getShortName());
+ }
wn.append(" [");
wn.append(en.getLocationAbbr(mounted.getLocation()));
//Check if mixedTech and add Clan or IS tag
diff --git a/megamek/src/megamek/client/ui/swing/util/KeyCommandBind.java b/megamek/src/megamek/client/ui/swing/util/KeyCommandBind.java
index baf35a27737..2b0f54f4ac1 100644
--- a/megamek/src/megamek/client/ui/swing/util/KeyCommandBind.java
+++ b/megamek/src/megamek/client/ui/swing/util/KeyCommandBind.java
@@ -90,6 +90,8 @@ public enum KeyCommandBind {
TOGGLE_CONVERSIONMODE("toggleConversion", VK_M),
PREV_MODE("prevMode", VK_KP_DOWN),
NEXT_MODE("nextMode", VK_KP_UP),
+ PAUSE("pause", VK_P, CTRL_DOWN_MASK | SHIFT_DOWN_MASK),
+ UNPAUSE("unpause", VK_P, CTRL_DOWN_MASK | ALT_DOWN_MASK),
// --------- The following binds are used by the CommonMenuBar:
// Toggles isometric view on/off
@@ -218,4 +220,4 @@ public static String getDesc(KeyCommandBind k) {
String key = getKeyText(k.key);
return (mod.isEmpty() ? "" : mod + "+") + key;
}
-}
\ No newline at end of file
+}
diff --git a/megamek/src/megamek/common/AmmoType.java b/megamek/src/megamek/common/AmmoType.java
index 41445f12e22..ce6db0e7700 100644
--- a/megamek/src/megamek/common/AmmoType.java
+++ b/megamek/src/megamek/common/AmmoType.java
@@ -484,6 +484,10 @@ public int getAmmoType() {
return ammoType;
}
+ public int getToHitModifier() {
+ return toHitModifier;
+ }
+
/**
* Analog to WeaponType.getFireTNRoll(), but based on munitions.
* See TO:AR pg 42
diff --git a/megamek/src/megamek/common/Board.java b/megamek/src/megamek/common/Board.java
index 66bd8facb55..427cce25fd8 100644
--- a/megamek/src/megamek/common/Board.java
+++ b/megamek/src/megamek/common/Board.java
@@ -21,11 +21,7 @@
package megamek.common;
import static java.util.stream.Collectors.toList;
-import static megamek.common.SpecialHexDisplay.Type.ARTILLERY_DRIFT;
-import static megamek.common.SpecialHexDisplay.Type.ARTILLERY_MISS;
-import static megamek.common.SpecialHexDisplay.Type.BOMB_DRIFT;
-import static megamek.common.SpecialHexDisplay.Type.BOMB_HIT;
-import static megamek.common.SpecialHexDisplay.Type.BOMB_MISS;
+import static megamek.common.SpecialHexDisplay.Type.*;
import java.io.*;
import java.util.*;
@@ -1752,7 +1748,7 @@ public Hashtable> getSpecialHexDisplayTabl
public void setSpecialHexDisplayTable(Hashtable> shd) {
Hashtable> temp = new Hashtable<>();
- // Grab all current ARTILLERY_MISS instances
+ // Grab all current ARTILLERY_MISS and ARTILLERY_DRIFT instances
for (Map.Entry> e : specialHexes.entrySet()) {
for (SpecialHexDisplay special : e.getValue()) {
if (Set.of(ARTILLERY_MISS, ARTILLERY_DRIFT).contains(special.getType())) {
@@ -2087,4 +2083,5 @@ public static int decodeCustomDeploymentZoneID(int zoneID) {
public static int encodeCustomDeploymentZoneID(int zoneID) {
return zoneID + NUM_ZONES_X2;
}
+
}
diff --git a/megamek/src/megamek/common/Compute.java b/megamek/src/megamek/common/Compute.java
index de4ef9701c4..02491143d28 100644
--- a/megamek/src/megamek/common/Compute.java
+++ b/megamek/src/megamek/common/Compute.java
@@ -7248,6 +7248,7 @@ public static boolean allowAimedShotWith(WeaponMounted weapon, AimingMode aiming
case TARGETING_COMPUTER:
if (!wtype.hasFlag(WeaponType.F_DIRECT_FIRE)
|| wtype.hasFlag(WeaponType.F_PULSE)
+ || weapon.curMode().getName().equals("Pulse")
|| (wtype instanceof HAGWeapon)) {
return false;
}
diff --git a/megamek/src/megamek/common/Configuration.java b/megamek/src/megamek/common/Configuration.java
index a69cd4fa62e..2500751ff84 100644
--- a/megamek/src/megamek/common/Configuration.java
+++ b/megamek/src/megamek/common/Configuration.java
@@ -121,6 +121,8 @@ public final class Configuration {
/** The default universe directory name (under the images directory). */
private static final String DEFAULT_DIR_NAME_IMG_UNIVERSE = "universe";
+ private static final String DEFAULT_DIR_ORBITAL_BOMBARDMENT = "orbital_bombardment";
+ private static final String DEFAULT_DIR_NUKE = "nuke";
private Configuration() {
}
@@ -326,6 +328,22 @@ public static File hexesDir() {
return new File(imagesDir(), DEFAULT_DIR_NAME_HEXES);
}
+ /**
+ * Return the orbital bombardment hexes directory, which is relative to the hexes directory.
+ * @return {@link File} containing the path to the orbital bombardment hexes directory.
+ */
+ public static File orbitalBombardmentHexesDir() {
+ return new File(hexesDir(), DEFAULT_DIR_ORBITAL_BOMBARDMENT);
+ }
+
+ /**
+ * Return the nuke hit hexes directory, which is relative to the hexes directory.
+ * @return {@link File} containing the path to the orbital bombardment hexes directory.
+ */
+ public static File nukeHexesDir() {
+ return new File(hexesDir(), DEFAULT_DIR_NUKE);
+ }
+
/**
* Get the fluff images directory, which is relative to the images
* directory.
diff --git a/megamek/src/megamek/common/Coords.java b/megamek/src/megamek/common/Coords.java
index 0e10fea0c3a..c1c89255494 100644
--- a/megamek/src/megamek/common/Coords.java
+++ b/megamek/src/megamek/common/Coords.java
@@ -1,6 +1,6 @@
/*
* MegaMek - Copyright (C) 2000-2002 Ben Mazur (bmazur@sev.org)
- * MegaMek - Copyright (C) 2020 - The MegaMek Team
+ * MegaMek - Copyright (C) 2020 - The MegaMek Team
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
@@ -31,9 +31,9 @@
* 0
* _____
* 5 / \ 1
- *-x / \ +x
- * \ /
- * 4 \_____/ 2
+ *-x / \ +x
+ * \ /
+ * 4 \_____/ 2
* 3
* +y
*/
@@ -46,7 +46,7 @@ public class Coords implements Serializable {
@XmlElement(name="x")
private final int x;
-
+
@XmlElement(name="y")
private final int y;
@@ -99,7 +99,7 @@ public Coords translated(String dir) {
return translated(intDir);
}
-
+
// The instance methods xInDir etc. make for convenient calls
// while the static xInDir etc. can be called to avoid Coords construction
@@ -111,12 +111,14 @@ public static int xInDir(int x, int y, int dir) {
/** Returns the x value of the Coords the given distance in the direction dir. */
public static int xInDir(int x, int y, int dir, int distance) {
switch (dir) {
- case 1:
- case 2:
+ case 1: // NE
+ case 2: // SE
return x + distance;
- case 4:
- case 5:
+ case 4: // NW
+ case 5: // SW
return x - distance;
+ case 0: // North
+ case 3: // South
default:
return x;
}
@@ -142,17 +144,16 @@ public static int yInDir(int x, int y, int dir, int distance) {
return y + distance;
}
}
-
+
/** Returns the x value of the adjacent Coords in the direction dir. */
public int xInDir(int dir) {
return Coords.xInDir(x, y, dir, 1);
}
-
/** Returns the x value of the Coords the given distance in the direction dir. */
public int xInDir(int dir, int distance) {
return Coords.xInDir(x, y, dir, distance);
}
-
+
/** Returns the y value of the adjacent Coords in the direction dir. */
public int yInDir(int dir) {
return Coords.yInDir(x, y, dir, 1);
@@ -160,7 +161,7 @@ public int yInDir(int dir) {
/** Returns the y value of the Coords the given distance in the direction dir. */
public int yInDir(int dir, int distance) {
- return Coords.yInDir(x, y, dir, distance);
+ return Coords.yInDir(x, y, dir, distance);
}
/**
@@ -175,24 +176,24 @@ public boolean isXOdd() {
/**
* Returns the direction in which another coordinate lies; 0 if the
* coordinates are equal.
- *
+ *
* @param d the destination coordinate.
*/
public int direction(Coords d) {
return (int) Math.round(radian(d) / HEXSIDE) % 6;
}
-
+
/**
- * Returns an approximate direction in which another coordinate lies;
+ * Returns an approximate direction in which another coordinate lies;
* 0 if the coordinates are equal
*/
public int approximateDirection(Coords second, int initialDirection, int previousDirection) {
if (this.equals(second)) {
return 0;
}
-
+
int direction = initialDirection;
-
+
HexLine startLine = new HexLine(this, direction);
int directionIncrement = 0;
int pointJudgement = startLine.judgePoint(second);
@@ -212,14 +213,14 @@ public int approximateDirection(Coords second, int initialDirection, int previou
} else if (pointJudgement > 0) {
directionIncrement = 1;
}
-
+
int newDirection = (initialDirection + directionIncrement) % 6;
if (newDirection == previousDirection) {
return newDirection;
} else {
return approximateDirection(second, newDirection, initialDirection);
}
-
+
// draw hexline in "direction".
// if dest is on hexline (judgePoint == 0), destDir = "direction"
// if judgepoint < 0, repeat with hexline in (direction - 1) % 6
@@ -228,7 +229,7 @@ public int approximateDirection(Coords second, int initialDirection, int previou
/**
* Returns the radian direction of another Coords.
- *
+ *
* @param d the destination coordinate.
*/
public double radian(Coords d) {
@@ -318,7 +319,7 @@ public boolean equals(Object object) {
@Override
public int hashCode() {
if (hash == 0) {
- hash = Objects.hash(x, y);
+ hash = Objects.hash(x, y);
}
return hash;
}
@@ -337,7 +338,7 @@ public String toString() {
* three hexes, sides first, add the first one that intersects and continue
* from there. Based off of some of the formulas at Amit's game programming
* site. (http://www-cs-students.stanford.edu/~amitp/gameprog.html)
- *
+ *
* Note: this function can return Coordinates that are not on the board.
*
* @param src Starting point.
@@ -485,44 +486,51 @@ public boolean isOnHexRow(int direction, @Nullable Coords other) {
}
/**
- * Returns a list of all adjacent coordinates (distance = 1),
+ * Returns a list of all adjacent coordinates (distance = 1),
* regardless of whether they're on the board or not.
*/
public ArrayList allAdjacent() {
return (allAtDistance(1));
}
-
+
+ /**
+ * Returns a list of all coordinates at the given distance (dist - 1)
+ * and anything less than dist as well.
+ */
+ public ArrayList allLessThanDistance(int dist) {
+ return allAtDistanceOrLess(dist - 1);
+ }
+
/**
- * Returns a list of all coordinates at the given distance dist
+ * Returns a list of all coordinates at the given distance dist
* and anything less than dist as well.
*/
public ArrayList allAtDistanceOrLess(int dist) {
ArrayList retval = new ArrayList<>();
-
- for (int radius = 0; radius < dist; radius++) {
+
+ for (int radius = 0; radius <= dist; radius++) {
retval.addAll(allAtDistance(radius));
}
-
+
return retval;
}
-
+
/**
- * Returns a list of all coordinates at the given distance dist,
- * regardless of whether they're on the board or not. Returns an
+ * Returns a list of all coordinates at the given distance dist,
+ * regardless of whether they're on the board or not. Returns an
* empty Set for dist < 0 and the calling Coords itself for dist == 0.
*/
- public ArrayList allAtDistance(int dist) {
+ public ArrayList allAtDistance(int dist) {
ArrayList retval = new ArrayList<>();
-
+
if (dist == 0) {
retval.add(this);
} else if (dist > 0) {
// algorithm outline: travel to the southwest a number of hexes equal to the radius
- // then, "draw" the hex sides in sequence, moving north first to draw the west side,
- // then rotating clockwise and moving northeast to draw the northwest side and so on,
+ // then, "draw" the hex sides in sequence, moving north first to draw the west side,
+ // then rotating clockwise and moving northeast to draw the northwest side and so on,
// until we circle around. The length of a hex side is equivalent to the radius
Coords currentHex = translated(4, dist);
-
for (int direction = 0; direction < 6; direction++) {
for (int translation = 0; translation < dist; translation++) {
currentHex = currentHex.translated(direction);
@@ -532,7 +540,7 @@ public ArrayList allAtDistance(int dist) {
}
return retval;
}
-
+
/**
* this makes the coordinates 1 based instead of 0 based to match the tiles
* diaplayed on the grid.
diff --git a/megamek/src/megamek/common/Entity.java b/megamek/src/megamek/common/Entity.java
index 06d10d45800..5a55bd9599a 100644
--- a/megamek/src/megamek/common/Entity.java
+++ b/megamek/src/megamek/common/Entity.java
@@ -4790,6 +4790,19 @@ public List getCriticalSlots(int location) {
return result;
}
+ /**
+ * @return true if the entity has any critical slot that isn't damaged yet
+ */
+ public boolean hasUndamagedCriticalSlots() {
+ return IntStream.range(0, locations())
+ .mapToLong(i -> getCriticalSlots(i)
+ .stream()
+ .filter(Objects::nonNull)
+ .filter(CriticalSlot::isHittable)
+ .count()
+ ).sum() > 0;
+ }
+
/**
* @return True when this unit has a RISC Super-Cooled Myomer System (even if
* the SCM is destroyed).
@@ -8918,7 +8931,7 @@ public String getUnusedString(ViewFormatting formatting) {
} else {
result.append(next.getUnusedString());
}
- if (isOmni() && ((next instanceof TroopSpace)
+ if (isOmni() && ((next instanceof InfantryCompartment)
|| (next instanceof Bay))) {
if (omniPodTransports.contains(next)) {
result.append(" (Pod)");
@@ -10123,8 +10136,8 @@ public boolean isEligibleForTargetingPhase() {
public double getTroopCarryingSpace() {
double space = 0;
for (Transporter t : transports) {
- if (t instanceof TroopSpace) {
- space += ((TroopSpace) t).totalSpace;
+ if (t instanceof InfantryCompartment) {
+ space += ((InfantryCompartment) t).totalSpace;
}
}
return space;
@@ -10133,8 +10146,8 @@ public double getTroopCarryingSpace() {
public double getPodMountedTroopCarryingSpace() {
double space = 0;
for (Transporter t : omniPodTransports) {
- if (t instanceof TroopSpace) {
- space += ((TroopSpace) t).totalSpace;
+ if (t instanceof InfantryCompartment) {
+ space += ((InfantryCompartment) t).totalSpace;
}
}
return space;
diff --git a/megamek/src/megamek/common/EquipmentType.java b/megamek/src/megamek/common/EquipmentType.java
index 746676431de..7c2cb86723b 100644
--- a/megamek/src/megamek/common/EquipmentType.java
+++ b/megamek/src/megamek/common/EquipmentType.java
@@ -502,7 +502,7 @@ public boolean isSpreadable() {
return spreadable;
}
- public int getToHitModifier() {
+ public int getToHitModifier(@Nullable Mounted> mounted) {
return toHitModifier;
}
@@ -555,6 +555,15 @@ public boolean hasModeType(String modeType) {
return false;
}
+ /**
+ * @param mounted The equipment mount. In some cases the moudes are affected by linked equipment.
+ * @return the number of modes that this type of equipment can be in or
+ * 0
if it doesn't have modes.
+ */
+ public int getModesCount(Mounted> mounted) {
+ return getModesCount();
+ }
+
/**
* @return the number of modes that this type of equipment can be in or
* 0
if it doesn't have modes.
diff --git a/megamek/src/megamek/common/Game.java b/megamek/src/megamek/common/Game.java
index 08f4a43ed56..1d50e446998 100644
--- a/megamek/src/megamek/common/Game.java
+++ b/megamek/src/megamek/common/Game.java
@@ -15,12 +15,6 @@
*/
package megamek.common;
-import static java.util.stream.Collectors.toList;
-
-import java.io.Serializable;
-import java.util.*;
-import java.util.concurrent.CopyOnWriteArrayList;
-
import megamek.MMConstants;
import megamek.Version;
import megamek.client.bot.princess.BehaviorSettings;
@@ -39,9 +33,16 @@
import megamek.common.weapons.AttackHandler;
import megamek.logging.MMLogger;
import megamek.server.SmokeCloud;
+import megamek.server.props.OrbitalBombardment;
import megamek.server.victory.VictoryHelper;
import megamek.server.victory.VictoryResult;
+import java.io.Serializable;
+import java.util.*;
+import java.util.concurrent.CopyOnWriteArrayList;
+
+import static java.util.stream.Collectors.toList;
+
/**
* The game class is the root of all data about the game in progress. Both the
* Client and the Server should have one of these objects, and it is their job
@@ -113,7 +114,7 @@ public final class Game extends AbstractGame implements Serializable, PlanetaryC
private Vector vibrabombs = new Vector<>();
private Vector attacks = new Vector<>();
private Vector offboardArtilleryAttacks = new Vector<>();
-
+ private Vector orbitalBombardmentAttacks = new Vector();
private int lastEntityId;
private Vector tagInfoForTurn = new Vector<>();
@@ -760,6 +761,7 @@ public void setLastPhase(GamePhase lastPhase) {
/**
* @return an enumeration of all the entities in the game.
+ * @deprecated Use {@link #inGameTWEntities()} instead.
*/
@Deprecated
public Iterator getEntities() {
@@ -2201,6 +2203,29 @@ public int removeSpecificEntityTurnsFor(Entity entity) {
return turnsToRemove.size();
}
+ /**
+ * Set the new vector of orbital bombardments for this round.
+ * @param orbitalBombardments
+ */
+ public void setOrbitalBombardmentVector(Vector orbitalBombardments) {
+ orbitalBombardmentAttacks = orbitalBombardments;
+ processGameEvent(new GameBoardChangeEvent(this));
+ }
+
+ /**
+ * Resets the orbital bombardment attacks list.
+ */
+ public void resetOrbitalBombardmentAttacks() {
+ orbitalBombardmentAttacks.removeAllElements();
+ }
+
+ /**
+ * @return an Enumeration of orbital bombardment attacks.
+ */
+ public Enumeration getOrbitalBombardmentAttacks() {
+ return orbitalBombardmentAttacks.elements();
+ }
+
public void setArtilleryVector(Vector v) {
offboardArtilleryAttacks = v;
processGameEvent(new GameBoardChangeEvent(this));
diff --git a/megamek/src/megamek/common/TroopSpace.java b/megamek/src/megamek/common/InfantryCompartment.java
similarity index 97%
rename from megamek/src/megamek/common/TroopSpace.java
rename to megamek/src/megamek/common/InfantryCompartment.java
index 82e6ba524d6..6d90120ca8d 100644
--- a/megamek/src/megamek/common/TroopSpace.java
+++ b/megamek/src/megamek/common/InfantryCompartment.java
@@ -23,14 +23,14 @@
* Represents a volume of space set aside for carrying troops and their equipment under battle
* conditions. Typically, a component of an APC.
*/
-public final class TroopSpace implements Transporter {
+public final class InfantryCompartment implements Transporter {
private static final long serialVersionUID = 7837499891552862932L;
/**
* The troops being carried.
*/
Map troops = new HashMap<>();
-
+
/**
* The total amount of space available for troops.
*/
@@ -46,7 +46,7 @@ public final class TroopSpace implements Transporter {
/**
* The default constructor is only for serialization.
*/
- private TroopSpace() {
+ private InfantryCompartment() {
totalSpace = 0;
currentSpace = 0;
}
@@ -58,7 +58,7 @@ private TroopSpace() {
*
* @param space The weight of troops (in tons) this space can carry.
*/
- public TroopSpace(double space) {
+ public InfantryCompartment(double space) {
totalSpace = space;
currentSpace = space;
}
@@ -125,7 +125,7 @@ public Vector getLoadedUnits() {
for (Map.Entry entry : troops.entrySet()) {
int key = entry.getKey();
Entity entity = game.getEntity(key);
-
+
if (entity != null) {
loaded.add(entity);
}
@@ -174,7 +174,7 @@ public boolean unload(Entity unit) {
*/
@Override
public String getUnusedString() {
- return "Troops - " + currentSpace + " tons";
+ return "Infantry Compartment - " + currentSpace + " tons";
}
@Override
@@ -240,7 +240,7 @@ public String toString() {
public void setGame(Game game) {
this.game = game;
}
-
+
@Override
public void resetTransporter() {
troops = new HashMap<>();
diff --git a/megamek/src/megamek/common/Mek.java b/megamek/src/megamek/common/Mek.java
index 66c6503b163..2f70958e8e2 100644
--- a/megamek/src/megamek/common/Mek.java
+++ b/megamek/src/megamek/common/Mek.java
@@ -4185,6 +4185,9 @@ public boolean canAssaultDrop() {
@Override
public boolean isLocationProhibited(Coords c, int currElevation) {
Hex hex = game.getBoard().getHex(c);
+ if (hex == null) {
+ return false;
+ }
if (hex.containsTerrain(Terrains.IMPASSABLE)) {
return true;
}
diff --git a/megamek/src/megamek/common/MiscType.java b/megamek/src/megamek/common/MiscType.java
index b65a155f412..a71343dc985 100644
--- a/megamek/src/megamek/common/MiscType.java
+++ b/megamek/src/megamek/common/MiscType.java
@@ -17,6 +17,8 @@
import java.math.BigInteger;
import java.text.NumberFormat;
+import megamek.common.equipment.MiscMounted;
+import megamek.common.equipment.WeaponMounted;
import megamek.common.miscGear.AntiMekGear;
import megamek.common.weapons.ppc.CLERPPC;
import megamek.common.weapons.ppc.ISERPPC;
@@ -962,10 +964,9 @@ public double getCost(Entity entity, boolean isArmored, int loc, double size) {
}
}
- for (Mounted> mo : entity.getMisc()) {
- MiscType mt = (MiscType) mo.getType();
- if (mt.hasFlag(MiscType.F_RISC_LASER_PULSE_MODULE)) {
- fTons += mo.getTonnage();
+ for (MiscMounted mounted : entity.getMisc()) {
+ if (mounted.getType().hasFlag(MiscType.F_RISC_LASER_PULSE_MODULE)) {
+ fTons += mounted.getTonnage();
}
}
if (getInternalName().equals("ISTargeting Computer")) {
@@ -1110,17 +1111,15 @@ public int getCriticals(Entity entity, double size) {
} else if (hasFlag(F_TARGCOMP)) {
// based on tonnage of direct_fire weaponry
double fTons = 0.0;
- for (Mounted> m : entity.getWeaponList()) {
- WeaponType wt = (WeaponType) m.getType();
- if (wt.hasFlag(WeaponType.F_DIRECT_FIRE)) {
+ for (WeaponMounted m : entity.getWeaponList()) {
+ if (m.getType().hasFlag(WeaponType.F_DIRECT_FIRE)) {
fTons += m.getTonnage();
}
}
- for (Mounted> mo : entity.getMisc()) {
- MiscType mt = (MiscType) mo.getType();
- if (mt.hasFlag(MiscType.F_RISC_LASER_PULSE_MODULE)) {
- fTons += mo.getTonnage();
+ for (MiscMounted mounted : entity.getMisc()) {
+ if (mounted.getType().hasFlag(MiscType.F_RISC_LASER_PULSE_MODULE)) {
+ fTons += mounted.getTonnage();
}
}
if (TechConstants.isClan(getTechLevel(entity.getTechLevelYear()))) {
@@ -7130,6 +7129,7 @@ public static MiscType createMASH() {
misc.name = "MASH Equipment";
misc.setInternalName(misc.name);
misc.addLookupName("MASH Core Component");
+ misc.addLookupName("MASH Operation Theater");
misc.tonnage = TONNAGE_VARIABLE;
misc.criticals = 1;
misc.cost = COST_VARIABLE;
diff --git a/megamek/src/megamek/common/Mounted.java b/megamek/src/megamek/common/Mounted.java
index f50b9e24885..cb5a95c9a5f 100644
--- a/megamek/src/megamek/common/Mounted.java
+++ b/megamek/src/megamek/common/Mounted.java
@@ -229,7 +229,7 @@ protected void setType(T type) {
}
public int getModesCount() {
- return getType().getModesCount();
+ return getType().getModesCount(this);
}
protected EquipmentMode getMode(int mode) {
diff --git a/megamek/src/megamek/common/MoveStep.java b/megamek/src/megamek/common/MoveStep.java
index 5efe3972837..8b31155680e 100644
--- a/megamek/src/megamek/common/MoveStep.java
+++ b/megamek/src/megamek/common/MoveStep.java
@@ -3323,7 +3323,9 @@ public boolean isMovementPossible(Game game, Coords src, int srcEl, CachedEntity
final Coords dest = getPosition();
final Hex destHex = game.getBoard().getHex(dest);
final Entity entity = getEntity();
-
+ if (destHex == null) {
+ return false;
+ }
if (null == dest) {
var ex = new IllegalStateException("Step has no position");
logger.error("", ex);
diff --git a/megamek/src/megamek/common/SpecialHexDisplay.java b/megamek/src/megamek/common/SpecialHexDisplay.java
index 170966e71dd..d2d45c49d87 100644
--- a/megamek/src/megamek/common/SpecialHexDisplay.java
+++ b/megamek/src/megamek/common/SpecialHexDisplay.java
@@ -15,6 +15,7 @@
package megamek.common;
import java.awt.Image;
+import java.io.File;
import java.io.Serializable;
import java.util.Objects;
@@ -23,14 +24,16 @@
import megamek.common.util.ImageUtil;
import megamek.common.util.fileUtils.MegaMekFile;
+import static megamek.client.ui.swing.tileset.TilesetManager.FILENAME_ORBITAL_BOMBARDMENT_INCOMING_IMAGE;
+
/**
* @author dirk
*/
public class SpecialHexDisplay implements Serializable {
private static final long serialVersionUID = 27470795993329492L;
-
+ public static final int LARGE_EXPLOSION_IMAGE_RADIUS = 4;
public enum Type {
- ARTILLERY_AUTOHIT(new MegaMekFile(Configuration.hexesDir(), "artyauto.gif").toString()) {
+ ARTILLERY_AUTOHIT(new MegaMekFile(Configuration.hexesDir(), "artyauto.gif")) {
@Override
public boolean drawBefore() {
return false;
@@ -41,7 +44,7 @@ public boolean drawAfter() {
return true;
}
},
- ARTILLERY_ADJUSTED(new MegaMekFile(Configuration.hexesDir(), "artyadj.gif").toString()) {
+ ARTILLERY_ADJUSTED(new MegaMekFile(Configuration.hexesDir(), "artyadj.gif")) {
@Override
public boolean drawBefore() {
return false;
@@ -52,79 +55,114 @@ public boolean drawAfter() {
return true;
}
},
- ARTILLERY_INCOMING(new MegaMekFile(Configuration.hexesDir(), "artyinc.gif").toString()),
- ARTILLERY_TARGET(new MegaMekFile(Configuration.hexesDir(), "artytarget.gif").toString()) {
+ ARTILLERY_INCOMING(new MegaMekFile(Configuration.hexesDir(), "artyinc.gif")),
+ ARTILLERY_TARGET(new MegaMekFile(Configuration.hexesDir(), "artytarget.gif")) {
@Override
public boolean drawBefore() {
return false;
}
},
- ARTILLERY_HIT(new MegaMekFile(Configuration.hexesDir(), "artyhit.gif").toString()) {
+ ARTILLERY_HIT(new MegaMekFile(Configuration.hexesDir(), "artyhit.gif")) {
@Override
public boolean drawBefore() {
return false;
}
},
- ARTILLERY_DRIFT(new MegaMekFile(Configuration.hexesDir(), "artydrift.gif").toString()) {
+ ARTILLERY_DRIFT(new MegaMekFile(Configuration.hexesDir(), "artydrift.gif")) {
@Override
public boolean drawBefore() {
return false;
}
},
- ARTILLERY_MISS(new MegaMekFile(Configuration.hexesDir(), "artymiss.gif").toString()) {
+ ARTILLERY_MISS(new MegaMekFile(Configuration.hexesDir(), "artymiss.gif")) {
@Override
public boolean drawBefore() {
return false;
}
},
- BOMB_HIT(new MegaMekFile(Configuration.hexesDir(), "bombhit.gif").toString()) {
+ BOMB_HIT(new MegaMekFile(Configuration.hexesDir(), "bombhit.gif")) {
@Override
public boolean drawBefore() {
return false;
}
},
- BOMB_DRIFT(new MegaMekFile(Configuration.hexesDir(), "bombdrift.gif").toString()) {
+ BOMB_DRIFT(new MegaMekFile(Configuration.hexesDir(), "bombdrift.gif")) {
@Override
public boolean drawBefore() {
return false;
}
},
- BOMB_MISS(new MegaMekFile(Configuration.hexesDir(), "bombmiss.gif").toString()) {
+ BOMB_MISS(new MegaMekFile(Configuration.hexesDir(), "bombmiss.gif")) {
@Override
public boolean drawBefore() {
return false;
}
},
- PLAYER_NOTE(new MegaMekFile(Configuration.hexesDir(), "note.png").toString()) {
+ PLAYER_NOTE(new MegaMekFile(Configuration.hexesDir(), "note.png")) {
+ @Override
+ public boolean drawAfter() {
+ return true;
+ }
+ },
+ NUKE_INCOMING(new MegaMekFile(Configuration.hexesDir(), "nukeinc.gif")),
+ NUKE_HIT(new MegaMekFile(Configuration.nukeHexesDir(), "hit")) {
@Override
public boolean drawBefore() {
+ return false;
+ }
+
+ @Override
+ public boolean useFolderStructure() {
return true;
}
+ },
+ ORBITAL_BOMBARDMENT_INCOMING(new MegaMekFile(Configuration.hexesDir(), "artyinc.gif")),
+ ORBITAL_BOMBARDMENT(new MegaMekFile(Configuration.orbitalBombardmentHexesDir(), "hit")) {
+ @Override
+ public boolean drawBefore() {
+ return false;
+ }
@Override
- public boolean drawAfter() {
+ public boolean useFolderStructure() {
return true;
}
};
private transient Image defaultImage;
- private final String defaultImagePath;
+ private final MegaMekFile defaultImagePath;
- Type(String iconPath) {
+ Type(MegaMekFile iconPath) {
defaultImagePath = iconPath;
}
public void init() {
- if (defaultImagePath != null) {
- defaultImage = ImageUtil.loadImageFromFile(defaultImagePath);
+ if (defaultImagePath == null) {
+ return;
}
+ defaultImage = ImageUtil.loadImageFromFile(defaultImagePath);
+ }
+ public boolean useFolderStructure() {
+ return false;
}
public Image getDefaultImage() {
return defaultImage;
}
+ /**
+ * Get the image for this type of special hex display.
+ * @param imageName The name of the image to get
+ * @return The image
+ */
+ public Image getImage(String imageName) {
+ if (this.useFolderStructure()) {
+ return ImageUtil.loadImageFromFile(new MegaMekFile(defaultImagePath.getFile(), imageName));
+ }
+ return defaultImage;
+ }
+
public void setDefaultImage(Image defaultImage) {
this.defaultImage = defaultImage;
}
@@ -155,8 +193,8 @@ public boolean drawAfter() {
private String info;
private Type type;
private int round;
-
private Player owner;
+ private String imageSignature;
private int obscured = SHD_OBSCURED_ALL;
@@ -177,6 +215,15 @@ public SpecialHexDisplay(Type type, int round, Player owner, String info, int ob
this.obscured = obscured;
}
+ public SpecialHexDisplay(Type type, int round, Player owner, String info, int obscured, String imageSignature) {
+ this.type = type;
+ this.info = info;
+ this.round = round;
+ this.owner = owner;
+ this.obscured = obscured;
+ this.imageSignature = imageSignature;
+ }
+
public boolean thisRound(int testRound) {
if (NO_ROUND == round) {
return true;
@@ -220,6 +267,13 @@ public Type getType() {
return type;
}
+ public Image getDefaultImage() {
+ if (type.useFolderStructure()) {
+ return type.getImage(imageSignature);
+ }
+ return type.getDefaultImage();
+ }
+
public void setType(Type type) {
this.type = type;
}
@@ -246,20 +300,21 @@ public int getObscuredLevel() {
* Determines whether this special hex should be obscured from the given
* Player
.
*
- * @param other
- * @return
+ * @param other The player to check for
+ * @return True if the special hex should be obscured
*/
public boolean isObscured(Player other) {
+ if (owner == null) {
+ return false;
+ }
if ((obscured == SHD_OBSCURED_OWNER) && owner.equals(other)) {
return false;
} else if ((obscured == SHD_OBSCURED_TEAM) && (other != null)
&& (owner.getTeam() == other.getTeam())) {
return false;
- } else if (obscured == SHD_OBSCURED_ALL) {
- return false;
- } else {
- return true;
}
+
+ return obscured != SHD_OBSCURED_ALL;
}
public void setObscured(int obscured) {
@@ -272,12 +327,12 @@ public void setObscured(int obscured) {
* display
* in the appropriate phase. Other bomb- or artillery-related graphics are
* optional.
- *
- * @param phase
- * @param curRound
- * @param playerChecking
- * @param guiPref
- * @return
+ *
+ * @param phase The current phase of the game
+ * @param curRound The current round
+ * @param playerChecking The player checking the display
+ * @param guiPref The GUI preferences
+ * @return True if the image should be displayed
*/
public boolean drawNow(GamePhase phase, int curRound, Player playerChecking, GUIPreferences guiPref) {
boolean shouldDisplay = thisRound(curRound)
@@ -324,8 +379,8 @@ public boolean drawNow(GamePhase phase, int curRound, Player playerChecking, GUI
}
/**
- * @param toPlayer
- * @return
+ * @param toPlayer The player to check
+ * @return True if the player is the owner of this Special Hex Display
*/
public boolean isOwner(Player toPlayer) {
return (owner == null) || owner.equals(toPlayer);
@@ -351,7 +406,7 @@ public int hashCode() {
@Override
public String toString() {
- return "SHD: " + type.name() + ", " + "round " + round + ", by "
- + owner.getName();
+ return "SHD: " + type.name() + ", " + "round " + round + (owner != null ? ", by "
+ + owner.getName() : "");
}
}
diff --git a/megamek/src/megamek/common/Tank.java b/megamek/src/megamek/common/Tank.java
index a5fece70f62..6aa3a0a4efd 100644
--- a/megamek/src/megamek/common/Tank.java
+++ b/megamek/src/megamek/common/Tank.java
@@ -2498,7 +2498,7 @@ public int getFreeSlots() {
// total)
boolean infantryBayCounted = false;
for (Transporter transport : getTransports()) {
- if (transport instanceof TroopSpace) {
+ if (transport instanceof InfantryCompartment) {
usedSlots++;
infantryBayCounted = true;
break;
diff --git a/megamek/src/megamek/common/WeaponType.java b/megamek/src/megamek/common/WeaponType.java
index 51be77a0c7a..3d4820e2712 100644
--- a/megamek/src/megamek/common/WeaponType.java
+++ b/megamek/src/megamek/common/WeaponType.java
@@ -805,8 +805,8 @@ && getLongRange() < AlphaStrikeElement.LONG_RANGE) {
damage = adjustBattleForceDamageForMinRange(damage);
}
- if (getToHitModifier() != 0) {
- damage -= damage * getToHitModifier() * 0.05;
+ if (getToHitModifier(null) != 0) {
+ damage -= damage * getToHitModifier(null) * 0.05;
}
}
diff --git a/megamek/src/megamek/common/actions/WeaponAttackAction.java b/megamek/src/megamek/common/actions/WeaponAttackAction.java
index 53d7fb3b136..1876be5a9cc 100644
--- a/megamek/src/megamek/common/actions/WeaponAttackAction.java
+++ b/megamek/src/megamek/common/actions/WeaponAttackAction.java
@@ -3193,8 +3193,8 @@ private static ToHitData compileWeaponToHitMods(Game game, Entity ae, Entity spo
}
// Flat to hit modifiers defined in WeaponType
- if (wtype.getToHitModifier() != 0) {
- int modifier = wtype.getToHitModifier();
+ if (wtype.getToHitModifier(weapon) != 0) {
+ int modifier = wtype.getToHitModifier(weapon);
if (wtype instanceof VariableSpeedPulseLaserWeapon) {
int nRange = ae.getPosition().distance(target.getPosition());
int[] nRanges = wtype.getRanges(weapon, ammo);
@@ -5243,8 +5243,8 @@ private static ToHitData artilleryDirectToHit(Game game, Entity ae, Targetable t
toHit.addModifier(ae.getHeatFiringModifier(), Messages.getString("WeaponAttackAction.Heat"));
}
// weapon to-hit modifier
- if (wtype.getToHitModifier() != 0) {
- toHit.addModifier(wtype.getToHitModifier(), Messages.getString("WeaponAttackAction.WeaponMod"));
+ if (wtype.getToHitModifier(weapon) != 0) {
+ toHit.addModifier(wtype.getToHitModifier(weapon), Messages.getString("WeaponAttackAction.WeaponMod"));
}
// ammo to-hit modifier
if (usesAmmo && (atype.getToHitModifier() != 0)) {
diff --git a/megamek/src/megamek/common/alphaStrike/conversion/ASSpecialAbilityConverter.java b/megamek/src/megamek/common/alphaStrike/conversion/ASSpecialAbilityConverter.java
index 9a848aed646..3362941fcec 100644
--- a/megamek/src/megamek/common/alphaStrike/conversion/ASSpecialAbilityConverter.java
+++ b/megamek/src/megamek/common/alphaStrike/conversion/ASSpecialAbilityConverter.java
@@ -450,7 +450,7 @@ protected void processTransports() {
assign("Docking Collar", DT, 1);
} else if (t instanceof InfantryBay) {
assign("Infantry Bay", IT, ((InfantryBay) t).getCapacity());
- } else if (t instanceof TroopSpace) {
+ } else if (t instanceof InfantryCompartment) {
assign("Troop Space", IT, t.getUnused());
} else if (t instanceof MekBay) {
assign("Mek Bay", MT, (int) ((MekBay) t).getCapacity());
diff --git a/megamek/src/megamek/common/equipment/WeaponMounted.java b/megamek/src/megamek/common/equipment/WeaponMounted.java
index 48b39f59bd1..390b0e9aaf3 100644
--- a/megamek/src/megamek/common/equipment/WeaponMounted.java
+++ b/megamek/src/megamek/common/equipment/WeaponMounted.java
@@ -131,6 +131,9 @@ && getLinkedBy().getType().hasFlag(
heat++;
}
}
+ if (curMode().equals("Pulse")) {
+ heat += 2;
+ }
return heat;
}
diff --git a/megamek/src/megamek/common/hexarea/CircleHexArea.java b/megamek/src/megamek/common/hexarea/CircleHexArea.java
index 15839071d38..9a2cd0e8bad 100644
--- a/megamek/src/megamek/common/hexarea/CircleHexArea.java
+++ b/megamek/src/megamek/common/hexarea/CircleHexArea.java
@@ -57,7 +57,8 @@ public boolean isSmall() {
@Override
public Set getCoords() {
if (isSmall()) {
- return new HashSet<>(center.allAtDistanceOrLess(radius));
+ // TODO: Check if this function is not suffering of an off-by-one error
+ return new HashSet<>(center.allLessThanDistance(radius));
} else {
return super.getCoords();
}
diff --git a/megamek/src/megamek/common/loaders/BLKFile.java b/megamek/src/megamek/common/loaders/BLKFile.java
index e29fa670d8c..bb3943dff05 100644
--- a/megamek/src/megamek/common/loaders/BLKFile.java
+++ b/megamek/src/megamek/common/loaders/BLKFile.java
@@ -1206,7 +1206,7 @@ protected void addTransports(Entity e) throws EntityLoadingException {
case "troopspace":
// Everything after the ':' should be the space's size.
double fsize = Double.parseDouble(numbers);
- e.addTransporter(new TroopSpace(fsize), isPod);
+ e.addTransporter(new InfantryCompartment(fsize), isPod);
break;
case "cargobay":
pbi = new ParsedBayInfo(numbers, usedBayNumbers);
diff --git a/megamek/src/megamek/common/net/enums/PacketCommand.java b/megamek/src/megamek/common/net/enums/PacketCommand.java
index 1b249ec1d58..3e37e6f71ad 100644
--- a/megamek/src/megamek/common/net/enums/PacketCommand.java
+++ b/megamek/src/megamek/common/net/enums/PacketCommand.java
@@ -44,6 +44,10 @@ public enum PacketCommand {
/** A packet setting a Client's ready status (S -> C) or updating the Server on the Client's status (C -> S). */
PLAYER_READY,
+ /** A packet telling the server to pause / unpause packet handling (to interrupt a Princess-only game) */
+ PAUSE,
+ UNPAUSE,
+
CHAT,
ENTITY_ADD,
ENTITY_REMOVE,
diff --git a/megamek/src/megamek/common/preference/ClientPreferences.java b/megamek/src/megamek/common/preference/ClientPreferences.java
index c4629e65c8c..75d3b4b7943 100644
--- a/megamek/src/megamek/common/preference/ClientPreferences.java
+++ b/megamek/src/megamek/common/preference/ClientPreferences.java
@@ -70,6 +70,8 @@ public class ClientPreferences extends PreferenceStoreProxy {
*/
public static final String USER_DIR = "UserDir";
+ public static final String MML_PATH = "MmlPath";
+
// endregion Variable Declarations
// region Constructors
@@ -103,6 +105,7 @@ public ClientPreferences(IPreferenceStore store) {
store.setDefault(IP_ADDRESSES_IN_CHAT, false);
store.setDefault(START_SEARCHLIGHTS_ON, true);
store.setDefault(USER_DIR, "");
+ store.setDefault(MML_PATH, "");
setLocale(store.getString(LOCALE));
setMekHitLocLog();
}
@@ -406,4 +409,12 @@ public void setUserDir(String userDir) {
}
store.setValue(USER_DIR, userDir);
}
+
+ public String getMmlPath() {
+ return store.getString(MML_PATH);
+ }
+
+ public void setMmlPath(String mmlPath) {
+ store.setValue(MML_PATH, mmlPath.isBlank() ? "" : new File(mmlPath).getAbsolutePath());
+ }
}
diff --git a/megamek/src/megamek/common/templates/TROView.java b/megamek/src/megamek/common/templates/TROView.java
index e30ebd14fd2..fef8b33fbd4 100644
--- a/megamek/src/megamek/common/templates/TROView.java
+++ b/megamek/src/megamek/common/templates/TROView.java
@@ -684,7 +684,7 @@ protected String formatLocationTableEntry(Entity entity, Mounted> mounted) {
*/
protected @Nullable Map formatTransporter(Transporter transporter, String loc) {
final Map retVal = new HashMap<>();
- if (transporter instanceof TroopSpace) {
+ if (transporter instanceof InfantryCompartment) {
retVal.put("name", Messages.getString("TROView.TroopSpace"));
retVal.put("tonnage", transporter.getUnused());
} else if (transporter instanceof Bay) {
diff --git a/megamek/src/megamek/common/util/ImageUtil.java b/megamek/src/megamek/common/util/ImageUtil.java
index 9acbaa38997..1896c3ffc06 100644
--- a/megamek/src/megamek/common/util/ImageUtil.java
+++ b/megamek/src/megamek/common/util/ImageUtil.java
@@ -43,6 +43,7 @@
import megamek.common.Coords;
import megamek.common.Report;
import megamek.common.annotations.Nullable;
+import megamek.common.util.fileUtils.MegaMekFile;
import megamek.logging.MMLogger;
/**
@@ -240,6 +241,24 @@ public static Image loadImageFromFile(String fileName) {
return failStandardImage();
}
+
+ /**
+ * Loads and returns the image of the given fileName. This method does not make
+ * sure the image
+ * is fully loaded, this must be done by the caller when necessary (the simplest
+ * way is to
+ * create a new ImageIcon with the image). If the image cannot be loaded for any
+ * reason,
+ * the method returns a placeholder image but not null.
+ *
+ * @param file The image as a MegaMekFile
+ * @return The image if possible, a placeholder image otherwise
+ */
+ public static Image loadImageFromFile(MegaMekFile file) {
+ return loadImageFromFile(file.toString());
+ }
+
+
private ImageUtil() {
}
diff --git a/megamek/src/megamek/common/util/StringUtil.java b/megamek/src/megamek/common/util/StringUtil.java
index aacd01bf59c..4db08e960a5 100644
--- a/megamek/src/megamek/common/util/StringUtil.java
+++ b/megamek/src/megamek/common/util/StringUtil.java
@@ -57,7 +57,7 @@ public static String makeLength(int s, int n) {
/**
* A utility for padding out a string with spaces.
- *
+ *
* @param s the string to pad
* @param n the desired length of the resultant string
* @param bRightJustify true if the string should be right justified
@@ -82,7 +82,7 @@ public static String makeLength(String s, int n, boolean bRightJustify) {
* the end. The format of the stamp is dictated by the client option
* "StampFormat", which must use the same formatting as Java's
* DateTimeFormatter class.
- *
+ *
* @param filename the String containing the filename (with extension)
* @return the filename with date/time stamp added
*/
@@ -183,4 +183,18 @@ public static float toFloat(String s, float i) {
return i;
}
}
+
+ /**
+ * Returns if value is between the start and end
+ */
+ public static boolean isBetween(double value, String sStart, String sEnd) {
+ if (sStart.isEmpty() && sEnd.isEmpty()) {
+ return true;
+ }
+
+ int iStart = StringUtil.toInt(sStart, Integer.MIN_VALUE);
+ int iEnd = StringUtil.toInt(sEnd, Integer.MAX_VALUE);
+
+ return (!(value < iStart)) && (!(value > iEnd));
+ }
}
diff --git a/megamek/src/megamek/common/util/fileUtils/MegaMekFile.java b/megamek/src/megamek/common/util/fileUtils/MegaMekFile.java
index 97e90376f02..9d87ee132e7 100644
--- a/megamek/src/megamek/common/util/fileUtils/MegaMekFile.java
+++ b/megamek/src/megamek/common/util/fileUtils/MegaMekFile.java
@@ -50,6 +50,14 @@ public MegaMekFile(String pathname) {
}
}
+ public boolean isDirectory() {
+ return this.getFile().isDirectory();
+ }
+
+ public boolean isFile() {
+ return this.getFile().isFile();
+ }
+
public File getFile() {
return file;
}
diff --git a/megamek/src/megamek/common/verifier/TestEntity.java b/megamek/src/megamek/common/verifier/TestEntity.java
index b6ceaf64840..8de948b2da0 100755
--- a/megamek/src/megamek/common/verifier/TestEntity.java
+++ b/megamek/src/megamek/common/verifier/TestEntity.java
@@ -32,6 +32,7 @@
import megamek.common.annotations.Nullable;
import megamek.common.enums.MPBoosters;
import megamek.common.equipment.ArmorType;
+import megamek.common.equipment.MiscMounted;
import megamek.common.equipment.WeaponMounted;
import megamek.common.util.StringUtil;
import megamek.common.weapons.battlearmor.BAFlamerWeapon;
@@ -788,16 +789,14 @@ public int calcMiscCrits(MiscType mt, double size) {
}
} else if (mt.hasFlag(MiscType.F_TARGCOMP)) {
double fTons = 0.0f;
- for (Mounted> mo : getEntity().getWeaponList()) {
- WeaponType wt = (WeaponType) mo.getType();
- if (wt.hasFlag(WeaponType.F_DIRECT_FIRE)) {
- fTons += mo.getTonnage();
+ for (WeaponMounted mounted : getEntity().getWeaponList()) {
+ if (mounted.getType().hasFlag(WeaponType.F_DIRECT_FIRE)) {
+ fTons += mounted.getTonnage();
}
}
- for (Mounted> mo : getEntity().getMisc()) {
- MiscType mt2 = (MiscType) mo.getType();
- if (mt2.hasFlag(MiscType.F_RISC_LASER_PULSE_MODULE)) {
- fTons += mo.getTonnage();
+ for (MiscMounted mounted : getEntity().getMisc()) {
+ if (mounted.getType().hasFlag(MiscType.F_RISC_LASER_PULSE_MODULE)) {
+ fTons += mounted.getTonnage();
}
}
double weight = 0.0f;
diff --git a/megamek/src/megamek/common/verifier/TestSupportVehicle.java b/megamek/src/megamek/common/verifier/TestSupportVehicle.java
index afdf415e453..4e0f8775af3 100644
--- a/megamek/src/megamek/common/verifier/TestSupportVehicle.java
+++ b/megamek/src/megamek/common/verifier/TestSupportVehicle.java
@@ -1512,7 +1512,7 @@ public StringBuffer printSlotCalculation() {
boolean troopSpaceFound = false;
for (Transporter transport : supportVee.getTransports()) {
- if ((transport instanceof TroopSpace) && !troopSpaceFound) {
+ if ((transport instanceof InfantryCompartment) && !troopSpaceFound) {
buff.append(StringUtil.makeLength("Troop Space", 30));
buff.append("1\n");
troopSpaceFound = true;
@@ -1702,7 +1702,7 @@ public int getCrewSlots() {
}
/**
- * Each distinct bay requires a slot, regardless of size. All {@link TroopSpace}
+ * Each distinct bay requires a slot, regardless of size. All {@link InfantryCompartment}
* is treated as a single bay.
*
* @return The number of slots required by transporters.
@@ -1714,7 +1714,7 @@ public int getTransportSlots() {
for (Transporter transporter : supportVee.getTransports()) {
if ((transporter instanceof Bay transportBay) && !transportBay.isQuarters()) {
slots++;
- } else if ((transporter instanceof TroopSpace) && !foundTroopSpace) {
+ } else if ((transporter instanceof InfantryCompartment) && !foundTroopSpace) {
slots++;
foundTroopSpace = true;
}
diff --git a/megamek/src/megamek/common/verifier/TestTank.java b/megamek/src/megamek/common/verifier/TestTank.java
index cd0ec0ed217..9a569837d4f 100755
--- a/megamek/src/megamek/common/verifier/TestTank.java
+++ b/megamek/src/megamek/common/verifier/TestTank.java
@@ -667,7 +667,7 @@ public StringBuffer printSlotCalculation() {
// total)
boolean infantryBayCounted = false;
for (Transporter transport : tank.getTransports()) {
- if (transport instanceof TroopSpace) {
+ if (transport instanceof InfantryCompartment) {
buff.append(StringUtil.makeLength("Troop Space", 30));
buff.append("1\n");
infantryBayCounted = true;
diff --git a/megamek/src/megamek/common/weapons/AreaEffectHelper.java b/megamek/src/megamek/common/weapons/AreaEffectHelper.java
index b9a4e04ca5d..857cfd8f4a2 100644
--- a/megamek/src/megamek/common/weapons/AreaEffectHelper.java
+++ b/megamek/src/megamek/common/weapons/AreaEffectHelper.java
@@ -13,13 +13,7 @@
*/
package megamek.common.weapons;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Enumeration;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Vector;
+import java.util.*;
import megamek.common.*;
import megamek.common.planetaryconditions.Atmosphere;
@@ -624,7 +618,7 @@ else if (ammo.getMunitionType().contains(AmmoType.Munitions.M_FLECHETTE)) {
* arty
* attack, -1 otherwise
* @param mineClear Whether or not we're clearing a minefield
- * @return
+ * @return A DamageFalloff object containing the damage and falloff values and if it is cluster or not
*/
public static DamageFalloff calculateDamageFallOff(AmmoType ammo, int attackingBA, boolean mineClear) {
if (ammo == null) {
@@ -697,21 +691,23 @@ public static DamageFalloff calculateDamageFallOff(AmmoType ammo, int attackingB
clusterMunitionsFlag = true;
} else if (ammo.getMunitionType().contains(AmmoType.Munitions.M_FLECHETTE)) {
- switch (ammo.getAmmoType()) {
+ falloff = switch (ammo.getAmmoType()) {
// for flechette, damage and falloff is number of d6, not absolute
// damage
- case AmmoType.T_LONG_TOM:
+ case AmmoType.T_LONG_TOM -> {
damage = 4;
- falloff = 2;
- break;
- case AmmoType.T_SNIPER:
+ yield 2;
+ }
+ case AmmoType.T_SNIPER -> {
damage = 2;
- falloff = 1;
- break;
- case AmmoType.T_THUMPER:
+ yield 1;
+ }
+ case AmmoType.T_THUMPER -> {
damage = 1;
- falloff = 1;
- }
+ yield 1;
+ }
+ default -> falloff;
+ };
// if this was a mine clearance, then it only affects the hex hit
} else if (mineClear) {
falloff = damage;
@@ -731,6 +727,11 @@ public static DamageFalloff calculateDamageFallOff(AmmoType ammo, int attackingB
*/
public static void doNuclearExplosion(Entity entity, Coords coords, int nukeType, Vector vPhaseReport,
TWGameManager gameManager) {
+
+ // this +1 is necessary because the drawNuke method subtracts 1 from them
+ int[] nukeArgs = { coords.getX() + 1, coords.getY() + 1};
+ gameManager.drawNukeHitOnBoard(nukeArgs);
+
NukeStats nukeStats = getNukeStats(nukeType);
if (nukeStats == null) {
@@ -805,6 +806,14 @@ public static void applyExplosionClusterDamageToEntity(Entity entity, int damage
HitData hit = entity.rollHitLocation(table, Compute.targetSideTable(position, entity));
vDesc.addAll(gameManager.damageEntity(entity, hit, cluster, false,
DamageType.IGNORE_PASSENGER, false, true));
+
+ // If there is nothing left to destroy in the unit
+ if (
+ entity.isDoomed()
+ && (!entity.hasUndamagedCriticalSlots() || entity.getRemovalCondition() == IEntityRemovalConditions.REMOVE_DEVASTATED)
+ ) {
+ break;
+ }
damage -= cluster;
}
}
diff --git a/megamek/src/megamek/common/weapons/PulseLaserWeaponHandler.java b/megamek/src/megamek/common/weapons/PulseLaserWeaponHandler.java
index 662c8a3ec47..07ce462ee3b 100644
--- a/megamek/src/megamek/common/weapons/PulseLaserWeaponHandler.java
+++ b/megamek/src/megamek/common/weapons/PulseLaserWeaponHandler.java
@@ -19,16 +19,14 @@
*/
package megamek.common.weapons;
-import megamek.common.BattleArmor;
-import megamek.common.Compute;
-import megamek.common.Game;
-import megamek.common.Infantry;
-import megamek.common.RangeType;
-import megamek.common.ToHitData;
+import megamek.common.*;
import megamek.common.actions.WeaponAttackAction;
+import megamek.common.equipment.WeaponMounted;
import megamek.common.options.OptionsConstants;
import megamek.server.totalwarfare.TWGameManager;
+import java.util.Vector;
+
public class PulseLaserWeaponHandler extends EnergyWeaponHandler {
private static final long serialVersionUID = -5701939682138221449L;
@@ -36,6 +34,20 @@ public PulseLaserWeaponHandler(ToHitData toHit, WeaponAttackAction waa, Game g,
super(toHit, waa, g, m);
}
+ @Override
+ protected boolean doChecks(Vector vPhaseReport) {
+ if (super.doChecks(vPhaseReport)) {
+ return true;
+ }
+
+ WeaponMounted laser = waa.getEntity(game).getWeapon(waa.getWeaponId());
+
+ if ((roll.getIntValue() == 2) && laser.curMode().equals("Pulse")) {
+ vPhaseReport.addAll(gameManager.explodeEquipment(laser.getEntity(), laser.getLocation(), laser.getLinkedBy()));
+ }
+ return false;
+ }
+
@Override
protected int calcDamagePerHit() {
double toReturn = wtype.getDamage();
@@ -70,7 +82,7 @@ protected int calcDamagePerHit() {
if (game.getOptions().booleanOption(OptionsConstants.ADVCOMBAT_TACOPS_LOS_RANGE)
&& (nRange > wtype.getRanges(weapon)[RangeType.RANGE_EXTREME])) {
toReturn = (int) Math.floor(toReturn / 3.0);
- }
+ }
if (target.isConventionalInfantry()) {
toReturn = Compute.directBlowInfantryDamage(toReturn,
@@ -81,7 +93,7 @@ protected int calcDamagePerHit() {
} else if (bDirect) {
toReturn = Math.min(toReturn + (toHit.getMoS() / 3), toReturn * 2);
}
-
+
toReturn = applyGlancingBlowModifier(toReturn, target.isConventionalInfantry());
return (int) Math.ceil(toReturn);
}
diff --git a/megamek/src/megamek/common/weapons/SRMAXHandler.java b/megamek/src/megamek/common/weapons/SRMAXHandler.java
index e047b52aee3..2276aeeb145 100644
--- a/megamek/src/megamek/common/weapons/SRMAXHandler.java
+++ b/megamek/src/megamek/common/weapons/SRMAXHandler.java
@@ -20,6 +20,7 @@
package megamek.common.weapons;
import megamek.common.Game;
+import megamek.common.HitData;
import megamek.common.ToHitData;
import megamek.common.actions.WeaponAttackAction;
import megamek.server.totalwarfare.TWGameManager;
@@ -38,5 +39,6 @@ public SRMAXHandler(ToHitData t, WeaponAttackAction w, Game g, TWGameManager m)
sSalvoType = " acid-head missile(s) ";
nSalvoBonus = -2;
damageType = DamageType.ACID;
+ generalDamageType = HitData.DAMAGE_IGNORES_DMG_REDUCTION;
}
}
diff --git a/megamek/src/megamek/common/weapons/lasers/LaserWeapon.java b/megamek/src/megamek/common/weapons/lasers/LaserWeapon.java
index d20d87ecc83..b58bfd66a4f 100644
--- a/megamek/src/megamek/common/weapons/lasers/LaserWeapon.java
+++ b/megamek/src/megamek/common/weapons/lasers/LaserWeapon.java
@@ -19,9 +19,14 @@
import megamek.common.Mounted;
import megamek.common.ToHitData;
import megamek.common.actions.WeaponAttackAction;
+import megamek.common.annotations.Nullable;
+import megamek.common.equipment.MiscMounted;
+import megamek.common.options.GameOptions;
+import megamek.common.options.OptionsConstants;
import megamek.common.weapons.AttackHandler;
import megamek.common.weapons.EnergyWeaponHandler;
import megamek.common.weapons.InsulatedLaserWeaponHandler;
+import megamek.common.weapons.PulseLaserWeaponHandler;
import megamek.server.totalwarfare.TWGameManager;
/**
@@ -39,21 +44,46 @@ public LaserWeapon() {
atClass = CLASS_LASER;
}
- /*
- * (non-Javadoc)
- *
- * @see
- * megamek.common.weapons.Weapon#getCorrectHandler(megamek.common.ToHitData,
- * megamek.common.actions.WeaponAttackAction, megamek.common.Game,
- * megamek.server.Server)
- */
+ @Override
+ public void adaptToGameOptions(GameOptions gOp) {
+ super.adaptToGameOptions(gOp);
+
+ if (!(this instanceof PulseLaserWeapon)) {
+ addMode("");
+ addMode("Pulse");
+ }
+ }
+
+ @Override
+ public int getModesCount(Mounted> mounted) {
+ Mounted> linkedBy = mounted.getLinkedBy();
+ if ((linkedBy instanceof MiscMounted)
+ && !linkedBy.isInoperable()
+ && ((MiscMounted) linkedBy).getType().hasFlag(MiscType.F_RISC_LASER_PULSE_MODULE)) {
+ return 2;
+ } else {
+ return 0;
+ }
+ }
+
+ @Override
+ public int getToHitModifier(@Nullable Mounted> mounted) {
+ if ((mounted == null) || !(mounted.getLinkedBy() instanceof MiscMounted) || !mounted.getLinkedBy().getType().hasFlag(MiscType.F_RISC_LASER_PULSE_MODULE)) {
+ return super.getToHitModifier(mounted);
+ }
+ return mounted.curMode().getName().equals("Pulse") ? -2 : 0;
+ }
+
@Override
protected AttackHandler getCorrectHandler(ToHitData toHit, WeaponAttackAction waa, Game game,
TWGameManager manager) {
Mounted> linkedBy = waa.getEntity(game).getEquipment(waa.getWeaponId()).getLinkedBy();
- if ((linkedBy != null) && !linkedBy.isInoperable()
- && linkedBy.getType().hasFlag(MiscType.F_LASER_INSULATOR)) {
- return new InsulatedLaserWeaponHandler(toHit, waa, game, manager);
+ if ((linkedBy != null) && !linkedBy.isInoperable()) {
+ if (linkedBy.getType().hasFlag(MiscType.F_LASER_INSULATOR)) {
+ return new InsulatedLaserWeaponHandler(toHit, waa, game, manager);
+ } else if (linkedBy.getType().hasFlag(MiscType.F_RISC_LASER_PULSE_MODULE)) {
+ return new PulseLaserWeaponHandler(toHit, waa, game, manager);
+ }
}
return new EnergyWeaponHandler(toHit, waa, game, manager);
}
diff --git a/megamek/src/megamek/common/weapons/ppc/PPCWeapon.java b/megamek/src/megamek/common/weapons/ppc/PPCWeapon.java
index 72c0d6f4fa6..e2ae8c4bc08 100644
--- a/megamek/src/megamek/common/weapons/ppc/PPCWeapon.java
+++ b/megamek/src/megamek/common/weapons/ppc/PPCWeapon.java
@@ -73,8 +73,8 @@ public double getBattleForceDamage(int range, Mounted> capacitor) {
if ((range == AlphaStrikeElement.SHORT_RANGE) && (getMinimumRange() > 0)) {
damage = adjustBattleForceDamageForMinRange(damage);
}
- if (getToHitModifier() != 0) {
- damage -= damage * getToHitModifier() * 0.05;
+ if (getToHitModifier(null) != 0) {
+ damage -= damage * getToHitModifier(null) * 0.05;
}
}
return damage / 10.0;
diff --git a/megamek/src/megamek/server/Server.java b/megamek/src/megamek/server/Server.java
index d62d8555e92..cc51549c8c2 100644
--- a/megamek/src/megamek/server/Server.java
+++ b/megamek/src/megamek/server/Server.java
@@ -209,6 +209,10 @@ public void run() {
private static final String WARGAMES_RESPONSE = "Let's play global thermonuclear war.";
private final ConnectionListener connectionListener = new ConnectionListener() {
+
+ private boolean isPaused = false;
+ private final List pausedWaitingList = new ArrayList<>();
+
/**
* Called when it is sensed that a connection has terminated.
*/
@@ -252,11 +256,34 @@ public void packetReceived(PacketReceivedEvent e) {
// Some packets should be handled immediately
handle(rp.getConnectionId(), rp.getPacket());
break;
- default:
+ case PAUSE:
+ if (!isPaused) {
+ logger.info("Pause packet received - pausing packet handling");
+ sendServerChat("Game is paused.");
+ }
+ isPaused = true;
+ break;
+ case UNPAUSE:
+ if (isPaused) {
+ logger.info("Unpause packet received - resuming packet handling");
+ sendServerChat("Game is resumed.");
+ }
+ isPaused = false;
synchronized (packetQueue) {
- packetQueue.add(rp);
+ packetQueue.addAll(pausedWaitingList);
packetQueue.notifyAll();
}
+ pausedWaitingList.clear();
+ break;
+ default:
+ if (isPaused) {
+ pausedWaitingList.add(rp);
+ } else {
+ synchronized (packetQueue) {
+ packetQueue.add(rp);
+ packetQueue.notifyAll();
+ }
+ }
break;
}
}
diff --git a/megamek/src/megamek/server/ServerHelper.java b/megamek/src/megamek/server/ServerHelper.java
index 45d8d59640b..1cc55567f16 100644
--- a/megamek/src/megamek/server/ServerHelper.java
+++ b/megamek/src/megamek/server/ServerHelper.java
@@ -32,7 +32,7 @@
/**
* This class contains computations carried out by the Server class.
* Methods put in here should be static and self-contained.
- *
+ *
* @author NickAragua
*/
public class ServerHelper {
@@ -40,7 +40,7 @@ public class ServerHelper {
* Determines if the given entity is an infantry unit in the given hex is "in
* the open"
* (and thus subject to double damage from attacks)
- *
+ *
* @param te Target entity.
* @param te_hex Hex where target entity is located.
* @param game The current {@link Game}
@@ -558,7 +558,7 @@ public static void detectMinefields(Game game, Vector vPhaseReport, TWGa
/**
* Checks for minefields within the entity's active probe range.
- *
+ *
* @return True if any minefields have been detected.
*/
public static boolean detectMinefields(Game game, Entity entity, Coords coords,
@@ -642,7 +642,8 @@ public static boolean detectHiddenUnits(Game game, Entity detector, Coords detec
// Get all hidden units in probe range
List hiddenUnits = new ArrayList<>();
- for (Coords coords : detectorCoords.allAtDistanceOrLess(probeRange)) {
+ // TODO: Check if this function is not suffering of an off-by-one error
+ for (Coords coords : detectorCoords.allLessThanDistance(probeRange)) {
for (Entity entity : game.getEntitiesVector(coords, true)) {
if (entity.isHidden() && entity.isEnemyOf(detector)) {
hiddenUnits.add(entity);
@@ -739,7 +740,7 @@ public static void clearBloodStalkers(Game game, int stalkeeID, TWGameManager ga
* of consecutive use, IO p.89. The first round of use means consecutiveRounds =
* 1; this is
* the minimum as 0 rounds of use would not trigger a roll.
- *
+ *
* @param consecutiveRounds The rounds the RHS has been used
* @return The roll target number to avoid failure
*/
diff --git a/megamek/src/megamek/server/commands/AllowGameMasterCommand.java b/megamek/src/megamek/server/commands/AllowGameMasterCommand.java
index 839ea433b00..b8189ae330c 100644
--- a/megamek/src/megamek/server/commands/AllowGameMasterCommand.java
+++ b/megamek/src/megamek/server/commands/AllowGameMasterCommand.java
@@ -34,7 +34,7 @@ public class AllowGameMasterCommand extends ServerCommand {
public AllowGameMasterCommand(Server server, TWGameManager gameManager) {
super(server, "allowGM", "Allows a player become Game Master "
- + "Usage: /allowGameMaster used in respond to another " +
+ + "Usage: /allowGM used in respond to another " +
"Player's request to become Game Master. All players assigned to" +
" a team must allow the change.");
this.gameManager = gameManager;
diff --git a/megamek/src/megamek/server/commands/ChangeOwnershipCommand.java b/megamek/src/megamek/server/commands/ChangeOwnershipCommand.java
new file mode 100644
index 00000000000..a8971ffc63e
--- /dev/null
+++ b/megamek/src/megamek/server/commands/ChangeOwnershipCommand.java
@@ -0,0 +1,70 @@
+/*
+ * MegaMek - Copyright (c) 2024 - The MegaMek Team. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+package megamek.server.commands;
+
+import megamek.client.ui.Messages;
+import megamek.common.Entity;
+import megamek.common.Player;
+import megamek.server.Server;
+import megamek.server.commands.arguments.Argument;
+import megamek.server.commands.arguments.IntegerArgument;
+import megamek.server.totalwarfare.TWGameManager;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The Server Command "/changeOwner" that will switch an entity's owner to another player.
+ *
+ * @author Luana Coppio
+ */
+public class ChangeOwnershipCommand extends GamemasterServerCommand {
+
+ public static final String UNIT_ID = "unitID";
+ public static final String PLAYER_ID = "playerID";
+
+ public ChangeOwnershipCommand(Server server, TWGameManager gameManager) {
+ super(server,
+ gameManager,
+ "changeOwner",
+ Messages.getString("Gamemaster.cmd.changeownership.help"),
+ Messages.getString("Gamemaster.cmd.changeownership.longName"));
+ }
+
+ @Override
+ public List> defineArguments() {
+ return List.of(
+ new IntegerArgument(UNIT_ID, Messages.getString("Gamemaster.cmd.changeownership.unitID")),
+ new IntegerArgument(PLAYER_ID, Messages.getString("Gamemaster.cmd.changeownership.playerID")));
+ }
+
+ @Override
+ protected void runAsGM(int connId, Map> args) {
+ IntegerArgument unitID = (IntegerArgument) args.get(UNIT_ID);
+ IntegerArgument playerID = (IntegerArgument) args.get(PLAYER_ID);
+
+ Entity ent = gameManager.getGame().getEntity(unitID.getValue());
+ Player player = server.getGame().getPlayer(playerID.getValue());
+ if (null == ent) {
+ server.sendServerChat(connId, Messages.getString("Gamemaster.cmd.changeownership.unitNotFound"));
+ } else if (null == player) {
+ server.sendServerChat(connId, Messages.getString("Gamemaster.cmd.changeownership.playerNotFound"));
+ } else if (player.getTeam() == Player.TEAM_UNASSIGNED) {
+ server.sendServerChat(connId, Messages.getString("Gamemaster.cmd.changeownership.playerUnassigned"));
+ } else {
+ server.sendServerChat(connId, Messages.getString("Gamemaster.cmd.changeownership.success", ent.getDisplayName(), player.getName()));
+ ent.setTraitorId(player.getId());
+ }
+ }
+}
diff --git a/megamek/src/megamek/server/commands/ChangeWeatherCommand.java b/megamek/src/megamek/server/commands/ChangeWeatherCommand.java
new file mode 100644
index 00000000000..657eaf5fe50
--- /dev/null
+++ b/megamek/src/megamek/server/commands/ChangeWeatherCommand.java
@@ -0,0 +1,112 @@
+/*
+ * MegaMek - Copyright (c) 2024 - The MegaMek Team. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+package megamek.server.commands;
+
+import megamek.client.ui.Messages;
+import megamek.common.planetaryconditions.*;
+import megamek.server.Server;
+import megamek.server.commands.arguments.Argument;
+import megamek.server.commands.arguments.OptionalEnumArgument;
+import megamek.server.totalwarfare.TWGameManager;
+
+import java.util.List;
+import java.util.Map;
+import java.util.function.BiConsumer;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+/**
+ * @author Luana Coppio
+ */
+public class ChangeWeatherCommand extends GamemasterServerCommand {
+
+ private static final String FOG = "fog";
+ private static final String LIGHT = "light";
+ private static final String WIND = "wind";
+ private static final String WIND_DIR = "winddir";
+ private static final String ATMO = "atmo";
+ private static final String BLOWSAND = "blowsand";
+ private static final String EMIS = "emi";
+ private static final String WEATHER = "weather";
+
+ /** Creates new ChangeWeatherCommand */
+ public ChangeWeatherCommand(Server server, TWGameManager gameManager) {
+ super(server, gameManager, WEATHER, Messages.getString("Gamemaster.cmd.changeweather.help"),
+ Messages.getString("Gamemaster.cmd.changeweather.longName"));
+ }
+
+ @Override
+ public List> defineArguments() {
+ return List.of(new OptionalEnumArgument<>(FOG, Messages.getString("Gamemaster.cmd.changeweather.fog"), Fog.class),
+ new OptionalEnumArgument<>(LIGHT, Messages.getString("Gamemaster.cmd.changeweather.light"), Light.class),
+ new OptionalEnumArgument<>(WIND, Messages.getString("Gamemaster.cmd.changeweather.wind"), Wind.class),
+ new OptionalEnumArgument<>(WIND_DIR, Messages.getString("Gamemaster.cmd.changeweather.winddir"), WindDirection.class),
+ new OptionalEnumArgument<>(ATMO, Messages.getString("Gamemaster.cmd.changeweather.atmo"), Atmosphere.class),
+ new OptionalEnumArgument<>(BLOWSAND, Messages.getString("Gamemaster.cmd.changeweather.blowsand"), BlowingSand.class),
+ new OptionalEnumArgument<>(EMIS, Messages.getString("Gamemaster.cmd.changeweather.emi"), EMI.class),
+ new OptionalEnumArgument<>(WEATHER, Messages.getString("Gamemaster.cmd.changeweather.weather"), Weather.class));
+ }
+
+ private record Condition>(Consumer setter, Function successMessage) {
+ @SuppressWarnings("unchecked")
+ public void updatePlanetaryCondition(Enum> value, int connId, Server server) {
+ setter.accept((E) value);
+ server.sendServerChat(connId, successMessage.apply((E) value));
+ }
+ }
+
+ /**
+ * Run this command with the arguments supplied
+ */
+ @Override
+ public void runAsGM(int connId, Map> args) {
+ if (getGameManager().getGame().getBoard().inSpace()) {
+ server.sendServerChat(connId, "There is no planetary conditions to change outside of a planet");
+ return;
+ }
+ var planetaryConditions = getGameManager().getGame().getPlanetaryConditions();
+ var conditions = getStringConditionMap(planetaryConditions);
+ conditions.forEach(updatePlanetaryConditions(connId, args));
+ getGameManager().getGame().setPlanetaryConditions(planetaryConditions);
+ }
+
+ private BiConsumer> updatePlanetaryConditions(int connId, Map> args) {
+ return (prefix, condition) -> {
+ if (args.containsKey(prefix) && ((OptionalEnumArgument>) args.get(prefix)).isPresent()) {
+ var value = ((OptionalEnumArgument>) args.get(prefix)).getValue();
+ condition.updatePlanetaryCondition(value, connId, server);
+ }
+ };
+ }
+
+ private static Map> getStringConditionMap(PlanetaryConditions planetaryConditions) {
+ return Map.of(
+ FOG, new Condition<>(planetaryConditions::setFog, value -> Messages.getString("Gamemaster.cmd.changeweather.fog.success")),
+ WIND, new Condition<>(planetaryConditions::setWind, value -> Messages.getString("Gamemaster.cmd.changeweather.wind.success")),
+ WIND_DIR, new Condition<>(planetaryConditions::setWindDirection, value -> Messages.getString("Gamemaster.cmd.changeweather.winddir.success")),
+ LIGHT, new Condition<>(planetaryConditions::setLight, value -> Messages.getString("Gamemaster.cmd.changeweather.light.success")),
+ ATMO, new Condition<>(planetaryConditions::setAtmosphere,
+ value -> value.equals(Atmosphere.VACUUM) ? Messages.getString("Gamemaster.cmd.changeweather.atmo.success0") :
+ Messages.getString("Gamemaster.cmd.changeweather.atmo.success")),
+ BLOWSAND, new Condition<>(planetaryConditions::setBlowingSand,
+ value -> value.equals(BlowingSand.BLOWING_SAND) ? Messages.getString("Gamemaster.cmd.changeweather.blowsand.success1") :
+ Messages.getString("Gamemaster.cmd.changeweather.blowsand.success")),
+ WEATHER, new Condition<>(planetaryConditions::setWeather,
+ value -> Messages.getString("Gamemaster.cmd.changeweather.weather.success")),
+ EMIS, new Condition<>(planetaryConditions::setEMI,
+ value -> value.equals(EMI.EMI) ? Messages.getString("Gamemaster.cmd.changeweather.emi.success1") :
+ Messages.getString("Gamemaster.cmd.changeweather.emi.success"))
+ );
+ }
+}
diff --git a/megamek/src/megamek/server/commands/DisasterCommand.java b/megamek/src/megamek/server/commands/DisasterCommand.java
new file mode 100644
index 00000000000..10642a42686
--- /dev/null
+++ b/megamek/src/megamek/server/commands/DisasterCommand.java
@@ -0,0 +1,202 @@
+/*
+ * MegaMek - Copyright (c) 2024 - The MegaMek Team. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+package megamek.server.commands;
+
+import megamek.client.ui.Messages;
+import megamek.common.Coords;
+import megamek.server.Server;
+import megamek.server.commands.arguments.Argument;
+import megamek.server.commands.arguments.EnumArgument;
+import megamek.server.totalwarfare.TWGameManager;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+/**
+ * @author Luana Coppio
+ */
+public class DisasterCommand extends GamemasterServerCommand {
+
+ public static final String TYPE = "type";
+ private static final Random random = new Random();
+ enum Disaster {
+
+ RANDOM {
+ @Override
+ public boolean canHappenAtAll() {
+ return false;
+ }
+ },
+ ORBITAL_BOMBARDMENT {
+ @Override
+ public boolean canHappenInSpace() {
+ return true;
+ }
+ },
+ ORBITAL_BOMBARDMENT_2 {
+ @Override
+ public boolean canHappenInSpace() {
+ return true;
+ }
+ },
+ ORBITAL_BOMBARDMENT_3 {
+ @Override
+ public boolean canHappenInSpace() {
+ return true;
+ }
+ },
+ TRAITOR {
+ @Override
+ public boolean canHappenInSpace() {
+ return true;
+ }
+ },
+ HURRICANE,
+ SANDSTORM,
+ LIGHTNING_STORM,
+ ICE_STORM,
+ ECLIPSE,
+ SOLAR_FLARE,
+ SMOG,
+ SUPERNOVA,
+ FIRESTORM;
+
+ public boolean canHappenInSpace() {
+ return false;
+ }
+
+ public boolean canHappenAtAll() {
+ return true;
+ }
+
+ public static Disaster getRandomDisaster() {
+ var ret = Arrays.stream(values()).filter(Disaster::canHappenAtAll).toList();
+ return ret.get(random.nextInt(ret.size()));
+ }
+
+ public static Disaster getRandomSpaceDisaster() {
+ // currently only the first 4 disasters can happen in space, since all the others are either fire
+ // or climatic events
+ var ret = Arrays.stream(values()).filter(Disaster::canHappenInSpace).toList();
+ return ret.get(random.nextInt(ret.size()));
+ }
+ }
+
+ public DisasterCommand(Server server, TWGameManager gameManager) {
+ super(server, gameManager, "disaster", Messages.getString("Gamemaster.cmd.disaster.help"),
+ Messages.getString("Gamemaster.cmd.disaster.longName"));
+ }
+
+ @Override
+ public List> defineArguments() {
+ return List.of(new EnumArgument<>(TYPE, Messages.getString("Gamemaster.cmd.disaster.type"), Disaster.class, Disaster.RANDOM));
+ }
+
+ private void runDisasterCommand(int connId, Disaster disaster) {
+ switch (disaster) {
+ case HURRICANE:
+ new ChangeWeatherCommand(server, gameManager).run(connId, new String[]{"weather", "wind=6", "winddir=6"});
+ server.sendServerChat("Hurricane incoming!");
+ break;
+ case LIGHTNING_STORM:
+ new ChangeWeatherCommand(server, gameManager).run(connId, new String[]{"weather", "weather=14"});
+ server.sendServerChat("Lightning storm incoming!");
+ break;
+ case ECLIPSE:
+ new ChangeWeatherCommand(server, gameManager).run(connId, new String[]{"weather", "light=4"});
+ server.sendServerChat("The sun is being eclipsed...");
+ break;
+ case SOLAR_FLARE:
+ new ChangeWeatherCommand(server, gameManager).run(connId, new String[]{"weather", "light=5", "emi=1"});
+ new FirestormCommand(server, gameManager).run(connId, new String[]{"firestorm", "1", "5"});
+ server.sendServerChat("Sensors warn of an imminent solar flare incoming! Expect some fires.");
+ break;
+ case SUPERNOVA:
+ new ChangeWeatherCommand(server, gameManager).run(connId, new String[]{"weather", "light=5", "emi=1", "atmo=2", "wind=0", "weather=0"});
+ new FirestormCommand(server, gameManager).run(connId, new String[]{"firestorm", "2", "75"});
+ server.sendServerChat("The star is going supernova!");
+ server.sendServerChat("Everything is on fire! We are doomed!");
+ break;
+
+ case SANDSTORM:
+ new ChangeWeatherCommand(server, gameManager).run(connId, new String[]{"weather", "blowsand=1", "wind=4", "winddir=6"});
+ server.sendServerChat("A sandstorm is approaching!");
+ break;
+ case ICE_STORM:
+ new ChangeWeatherCommand(server, gameManager).run(connId, new String[]{"weather", "fog=1", "weather=11", "wind=6", "winddir=6"});
+ server.sendServerChat("An ice storm is incoming!");
+ break;
+ case FIRESTORM:
+ new FirestormCommand(server, gameManager).run(connId, new String[]{"firestorm", "2", "50"});
+ server.sendServerChat("A firestorm is consuming the battlefield!");
+ break;
+ case SMOG:
+ new ChangeWeatherCommand(server, gameManager).run(connId, new String[]{"weather", "atmo=5", "fog=2", "light=1"});
+ server.sendServerChat("A thick smog is covering the battlefield!");
+ break;
+ case TRAITOR:
+ var players = gameManager.getGame().getPlayersList();
+ var randomPlayer = players.get((random.nextInt(players.size())));
+ var units = gameManager.getGame().getPlayerEntities(randomPlayer, true);
+ var randomUnit = units.get((random.nextInt(units.size())));
+ var otherPlayers = players.stream().filter(p -> p != randomPlayer).toList();
+ var newOwner = otherPlayers.get(random.nextInt(otherPlayers.size()));
+ new ChangeOwnershipCommand(server, gameManager).run(connId,
+ new String[]{"traitor", "" + randomUnit.getId(), "" + newOwner.getId()});
+ server.sendServerChat("A traitor has been revealed!");
+ break;
+ case ORBITAL_BOMBARDMENT_3:
+ orbitalBombardment(connId);
+ case ORBITAL_BOMBARDMENT_2:
+ orbitalBombardment(connId);
+ case ORBITAL_BOMBARDMENT:
+ default:
+ orbitalBombardment(connId);
+ break;
+ }
+ }
+
+ private Coords getRandomHexCoords() {
+ var board = gameManager.getGame().getBoard();
+ var x = random.nextInt(board.getWidth());
+ var y = random.nextInt(board.getHeight());
+ return new Coords(x, y);
+ }
+
+ private void orbitalBombardment(int connId) {
+ var coords = getRandomHexCoords();
+ new OrbitalBombardmentCommand(server, gameManager).run(connId, new String[]{"ob",
+ coords.getX() + 1 + "",
+ coords.getY() + 1 + ""
+ });
+ }
+
+ /**
+ * Run this command with the arguments supplied
+ */
+ @Override
+ protected void runAsGM(int connId, Map> args) {
+ if (args.get(TYPE).getValue().equals(Disaster.RANDOM)) {
+ if (getGameManager().getGame().getBoard().inSpace()) {
+ runDisasterCommand(connId, Disaster.getRandomSpaceDisaster());
+ } else {
+ runDisasterCommand(connId, Disaster.getRandomDisaster());
+ }
+ } else {
+ runDisasterCommand(connId, (Disaster) args.get(TYPE).getValue());
+ }
+ }
+}
diff --git a/megamek/src/megamek/server/commands/FirefightCommand.java b/megamek/src/megamek/server/commands/FirefightCommand.java
new file mode 100644
index 00000000000..956f3508d1b
--- /dev/null
+++ b/megamek/src/megamek/server/commands/FirefightCommand.java
@@ -0,0 +1,77 @@
+/*
+ * MegaMek - Copyright (c) 2024 - The MegaMek Team. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+package megamek.server.commands;
+
+import megamek.client.ui.Messages;
+import megamek.common.Coords;
+import megamek.common.Hex;
+import megamek.server.Server;
+import megamek.server.commands.arguments.Argument;
+import megamek.server.commands.arguments.IntegerArgument;
+import megamek.server.totalwarfare.TWGameManager;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * The Server Command "/firefight" that will put one hex on fire.
+ *
+ * @author Luana Coppio
+ */
+public class FirefightCommand extends GamemasterServerCommand {
+
+ private static final String FIRESTARTER = "firefight";
+ private static final String X = "x";
+ private static final String Y = "y";
+ private static final String TYPE = "type";
+
+ public FirefightCommand(Server server, TWGameManager gameManager) {
+ super(server,
+ gameManager,
+ FIRESTARTER,
+ Messages.getString("Gamemaster.cmd.firefight.help"),
+ Messages.getString("Gamemaster.cmd.firefight.longName"));
+ }
+
+ @Override
+ public List> defineArguments() {
+ return List.of(
+ new IntegerArgument(X, Messages.getString("Gamemaster.cmd.x")),
+ new IntegerArgument(Y, Messages.getString("Gamemaster.cmd.y"))
+ );
+ }
+
+ /**
+ * Run this command with the arguments supplied
+ *
+ * @see ServerCommand#run(int, String[])
+ */
+ @Override
+ protected void runAsGM(int connId, Map> args) {
+ int xArg = (int) args.get(X).getValue() - 1;
+ int yArg = (int) args.get(Y).getValue() - 1;
+ firefight(new Coords(xArg, yArg));
+ }
+
+ private void firefight(Coords coords) {
+ try {
+ Hex hex = gameManager.getGame().getBoard().getHex(coords);
+ Objects.requireNonNull(hex, "Hex not found.");
+ gameManager.removeFire(coords, Messages.getString("Gamemaster.cmd.firefight.reason"));
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Failed to ignite hex: " + e.getMessage());
+ }
+ }
+}
diff --git a/megamek/src/megamek/server/commands/FirestarterCommand.java b/megamek/src/megamek/server/commands/FirestarterCommand.java
new file mode 100644
index 00000000000..be52e9ba099
--- /dev/null
+++ b/megamek/src/megamek/server/commands/FirestarterCommand.java
@@ -0,0 +1,82 @@
+/*
+ * MegaMek - Copyright (c) 2024 - The MegaMek Team. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+package megamek.server.commands;
+
+import megamek.client.ui.Messages;
+import megamek.common.Coords;
+import megamek.common.Hex;
+import megamek.server.Server;
+import megamek.server.commands.arguments.Argument;
+import megamek.server.commands.arguments.IntegerArgument;
+import megamek.server.totalwarfare.TWGameManager;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/**
+ * The Server Command "/firestarter" that will put one hex on fire.
+ *
+ * @author Luana Coppio
+ */
+public class FirestarterCommand extends GamemasterServerCommand {
+
+ private static final String FIRESTARTER = "firestarter";
+ private static final String X = "x";
+ private static final String Y = "y";
+ private static final String TYPE = "type";
+
+ public FirestarterCommand(Server server, TWGameManager gameManager) {
+ super(server,
+ gameManager,
+ FIRESTARTER,
+ Messages.getString("Gamemaster.cmd.fire.help"),
+ Messages.getString("Gamemaster.cmd.firestarter.longName"));
+ }
+
+ @Override
+ public List> defineArguments() {
+ return List.of(
+ new IntegerArgument(X, Messages.getString("Gamemaster.cmd.x")),
+ new IntegerArgument(Y, Messages.getString("Gamemaster.cmd.y")),
+ new IntegerArgument(TYPE, Messages.getString("Gamemaster.cmd.fire.type"), 1, 4, 1));
+ }
+
+ /**
+ * Run this command with the arguments supplied
+ *
+ * @see ServerCommand#run(int, String[])
+ */
+ @Override
+ protected void runAsGM(int connId, Map> args) {
+ if (getGameManager().getGame().getBoard().inSpace()) {
+ server.sendServerChat(connId, "Can't start a fire in space");
+ return;
+ }
+ int xArg = (int) args.get(X).getValue() - 1;
+ int yArg = (int) args.get(Y).getValue() - 1;
+ int fireType = (int) args.get(TYPE).getValue();
+ igniteHex(new Coords(xArg, yArg), fireType);
+ }
+
+ private void igniteHex(Coords coords, int fireType) {
+ try {
+ Hex hex = gameManager.getGame().getBoard().getHex(coords);
+ Objects.requireNonNull(hex, "Hex not found.");
+ gameManager.ignite(coords, fireType, gameManager.getvPhaseReport());
+ } catch (Exception e) {
+ throw new IllegalArgumentException("Failed to ignite hex: " + e.getMessage());
+ }
+ }
+}
diff --git a/megamek/src/megamek/server/commands/FirestormCommand.java b/megamek/src/megamek/server/commands/FirestormCommand.java
new file mode 100644
index 00000000000..c85eda5738b
--- /dev/null
+++ b/megamek/src/megamek/server/commands/FirestormCommand.java
@@ -0,0 +1,106 @@
+/*
+ * MegaMek - Copyright (c) 2024 - The MegaMek Team. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+package megamek.server.commands;
+
+import megamek.client.ui.Messages;
+import megamek.common.Coords;
+import megamek.common.Hex;
+import megamek.server.Server;
+import megamek.server.commands.arguments.Argument;
+import megamek.server.commands.arguments.IntegerArgument;
+import megamek.server.totalwarfare.TWGameManager;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The Server Command "/firestorm" that starts a blazing inferno on the board.
+ *
+ * @author Luana Coppio
+ */
+public class FirestormCommand extends GamemasterServerCommand {
+
+ private static final String FIRESTORM = "firestorm";
+ private static final String TYPE = "type";
+ private static final String PERCENT = "percent";
+
+ public FirestormCommand(Server server, TWGameManager gameManager) {
+ super(server,
+ gameManager,
+ FIRESTORM,
+ Messages.getString("Gamemaster.cmd.firestorm.help"),
+ Messages.getString("Gamemaster.cmd.firestorm.longName"));
+ }
+
+ @Override
+ public List> defineArguments() {
+ return List.of(
+ new IntegerArgument(TYPE, Messages.getString("Gamemaster.cmd.fire.type"), 1, 4, 1),
+ new IntegerArgument(PERCENT, Messages.getString("Gamemaster.cmd.fire.percent"), 1, 100, 25)
+ );
+ }
+
+ /**
+ * Run this command with the arguments supplied
+ *
+ * @see ServerCommand#run(int, String[])
+ */
+ @Override
+ protected void runAsGM(int connId, Map> args) {
+ if (getGameManager().getGame().getBoard().inSpace()) {
+ server.sendServerChat(connId, "Can't start a firestorm in space");
+ }
+ try {
+ var fireType = (int) args.get(TYPE).getValue();
+ var percent = (int) args.get(PERCENT).getValue();
+ var coords = getRandomCoords(numberOfCoordsFromPercent(percent));
+ coords.forEach(c -> igniteHex(c, fireType));
+ } catch (Exception e) {
+ logger.error(Messages.getString("Gamemaster.cmd.fire.failed"), e);
+ server.sendServerChat(connId, Messages.getString("Gamemaster.cmd.fire.failed"));
+ }
+ }
+
+ private int numberOfCoordsFromPercent(int percent) {
+ var boardHeight = gameManager.getGame().getBoard().getHeight();
+ var boardWidth = gameManager.getGame().getBoard().getWidth();
+ return Math.max((boardWidth * boardHeight * percent / 100), 1);
+ }
+
+ private HashSet getRandomCoords(int size) {
+ var boardHeight = gameManager.getGame().getBoard().getHeight();
+ var boardWidth = gameManager.getGame().getBoard().getWidth();
+ var coordsSet = new HashSet();
+ var maxTries = size * 10;
+ while (coordsSet.size() < size && maxTries > 0) {
+ var x = (int) (Math.random() * boardWidth);
+ var y = (int) (Math.random() * boardHeight);
+ coordsSet.add(new Coords(x, y));
+ maxTries--;
+ }
+
+ return coordsSet;
+ }
+
+ private void igniteHex(Coords coords, int fireType) {
+ Hex hex = gameManager.getGame().getBoard().getHex(coords);
+ if (null == hex) {
+ // Just ignore null hexes...
+ // they should not happen, but I don't want to crash the command
+ return;
+ }
+ gameManager.ignite(coords, fireType, gameManager.getvPhaseReport());
+ }
+}
diff --git a/megamek/src/megamek/server/commands/GamemasterServerCommand.java b/megamek/src/megamek/server/commands/GamemasterServerCommand.java
new file mode 100644
index 00000000000..dc346a6a147
--- /dev/null
+++ b/megamek/src/megamek/server/commands/GamemasterServerCommand.java
@@ -0,0 +1,200 @@
+/*
+ * MegaMek - Copyright (c) 2024 - The MegaMek Team. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+package megamek.server.commands;
+
+import megamek.client.ui.Messages;
+import megamek.logging.MMLogger;
+import megamek.server.Server;
+import megamek.server.commands.arguments.Argument;
+import megamek.server.totalwarfare.TWGameManager;
+
+import java.util.*;
+
+/**
+ * A ServerCommand that can only be used by Game Masters,
+ * This abstract class implements many features that are common to all Game Master commands,
+ * like the isGM check for users, it also uses the Argument class for building the command arguments
+ * and to abstract the parsing of the arguments, limit assertion and error handling, and for building
+ * a more dynamic "help" feature.
+ * It also has a more advanced parser and argument handling than the ServerCommand class, which allows for
+ * named arguments, positional arguments, optional arguments and default values.
+ * named arguments can be passed in any order, and positional arguments are parsed in order and MUST appear before named
+ * arguments.
+ *
+ * @author Luana Coppio
+ */
+public abstract class GamemasterServerCommand extends ServerCommand {
+ private static final String NEWLINE = "\n";
+ private static final String WHITESPACE = " ";
+ private static final String LONG_WHITESPACE = " ";
+ private static final String EMPTY_ARGUMENT = null;
+ protected final TWGameManager gameManager;
+ protected final static MMLogger logger = MMLogger.create(GamemasterServerCommand.class);
+ private final String errorMsg;
+ private final String longName;
+
+ /**
+ * Creates new ServerCommand that can only be used by Game Masters
+ *
+ * @param server instance of the server
+ * @param gameManager instance of the game manager
+ * @param name the name of the command
+ * @param helpText the help text for the command
+ */
+ public GamemasterServerCommand(Server server, TWGameManager gameManager, String name, String helpText, String longName) {
+ super(server, name, helpText);
+ this.gameManager = gameManager;
+ this.errorMsg = "Error executing command: " + name;
+ this.longName = longName;
+ }
+
+ private boolean isGM(int connId) {
+ return server.getGameManager().getGame().getPlayer(connId).getGameMaster();
+ }
+
+ protected TWGameManager getGameManager() {
+ return gameManager;
+ }
+
+ @Override
+ public void run(int connId, String[] args) {
+ if (!isGM(connId)) {
+ server.sendServerChat(connId, "This command can only be used by a game master.");
+ return;
+ }
+
+ try {
+ Map> parsedArguments = parseArguments(args);
+ runAsGM(connId, parsedArguments);
+ } catch (IllegalArgumentException e) {
+ server.sendServerChat(connId, "Invalid arguments: " + e.getMessage() + "\nUsage: " + this.getHelp());
+ } catch (Exception e) {
+ server.sendServerChat(connId, "An error occurred while executing the command. Check the log for more information");
+ logger.error(errorMsg, e);
+ }
+ }
+
+ // Method to parse arguments, to be implemented by the specific command class
+ public abstract List> defineArguments();
+
+
+ // Parses the arguments using the definition
+ private Map> parseArguments(String[] args) {
+
+ List> argumentDefinitions = defineArguments();
+ Map> parsedArguments = new HashMap<>();
+ List positionalArguments = new ArrayList<>();
+
+ // Map argument names to definitions for easy lookup
+ Map> argumentMap = new HashMap<>();
+ for (Argument> argument : argumentDefinitions) {
+ argumentMap.put(argument.getName(), argument);
+ }
+
+ // Separate positional arguments and named arguments
+ boolean namedArgumentStarted = false;
+ for (int i = 1; i < args.length; i++) {
+ String arg = args[i];
+ String[] keyValue = arg.split("=");
+
+ if (keyValue.length == 2) {
+ // Handle named arguments
+ namedArgumentStarted = true;
+ String key = keyValue[0];
+ String value = keyValue[1];
+
+ if (!argumentMap.containsKey(key)) {
+ throw new IllegalArgumentException("Unknown argument: " + key);
+ }
+
+ Argument> argument = argumentMap.get(key);
+ argument.parse(value);
+ parsedArguments.put(key, argument);
+ } else {
+ // Handle positional arguments
+ if (namedArgumentStarted) {
+ throw new IllegalArgumentException("Positional arguments cannot come after named arguments.");
+ }
+ positionalArguments.add(arg);
+ }
+ }
+
+ // Parse positional arguments
+ int index = 0;
+ for (Argument> argument : argumentDefinitions) {
+ if (parsedArguments.containsKey(argument.getName())) {
+ continue;
+ }
+ if (index < positionalArguments.size()) {
+ String value = positionalArguments.get(index);
+ argument.parse(value);
+ parsedArguments.put(argument.getName(), argument);
+ index++;
+ } else {
+ // designed to throw an error if the arg doesn't have a default value
+ argument.parse(EMPTY_ARGUMENT);
+ parsedArguments.put(argument.getName(), argument);
+ }
+ }
+
+ return parsedArguments;
+ }
+
+ public String getHelpHtml() {
+ return "" +
+ this.getHelp()
+ .replaceAll("<", "<")
+ .replaceAll(">", ">")
+ .replaceAll(LONG_WHITESPACE, "| ")
+ .replaceAll(NEWLINE, "
")+
+ "";
+ }
+
+ @Override
+ public String getHelp() {
+ StringBuilder help = new StringBuilder();
+ help.append(super.getHelp())
+ .append(NEWLINE)
+ .append(Messages.getString("Gamemaster.cmd.help"))
+ .append(NEWLINE)
+ .append(NEWLINE)
+ .append("/")
+ .append(getName());
+
+ for (Argument> arg : defineArguments()) {
+ help.append(WHITESPACE)
+ .append(arg.getRepr());
+ }
+
+ help.append(NEWLINE)
+ .append(NEWLINE);
+
+ for (var arg : defineArguments()) {
+ help.append(LONG_WHITESPACE)
+ .append(arg.getName())
+ .append(":")
+ .append(WHITESPACE)
+ .append(arg.getHelp())
+ .append(NEWLINE);
+ }
+ return help.toString();
+ }
+
+ public String getLongName() {
+ return longName;
+ }
+
+ // The new method for game master commands that uses parsed arguments
+ protected abstract void runAsGM(int connId, Map> args);
+}
diff --git a/megamek/src/megamek/server/commands/HelpCommand.java b/megamek/src/megamek/server/commands/HelpCommand.java
index 8c68edde3d3..ceaeb93c94c 100644
--- a/megamek/src/megamek/server/commands/HelpCommand.java
+++ b/megamek/src/megamek/server/commands/HelpCommand.java
@@ -22,7 +22,7 @@
* The help command lists the other commands when run without arguments. When
* run with another command name as an argument, it queries that command for its
* help string and send that to the client.
- *
+ *
* @author Ben
* @since March 30, 2002, 7:03 PM
*/
@@ -51,8 +51,10 @@ public void run(int connId, String[] args) {
+ "\" not recognized. Commands available: "
+ commandList());
} else {
- server.sendServerChat(connId, "/" + command.getName() + " : "
- + command.getHelp());
+ var help = command.getHelp();
+ for (String line : help.split("\n")) {
+ server.sendServerChat(connId, line);
+ }
}
}
}
@@ -64,7 +66,7 @@ private String commandList() {
Collections.sort(cmdNames);
for (String cmdName : cmdNames) {
- if (commandList.length() > 0) {
+ if (!commandList.isEmpty()) {
commandList.append(", ");
}
commandList.append(cmdName);
diff --git a/megamek/src/megamek/server/commands/KillCommand.java b/megamek/src/megamek/server/commands/KillCommand.java
new file mode 100644
index 00000000000..bbbe191067c
--- /dev/null
+++ b/megamek/src/megamek/server/commands/KillCommand.java
@@ -0,0 +1,58 @@
+/*
+ * MegaMek - Copyright (c) 2024 - The MegaMek Team. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+package megamek.server.commands;
+
+import megamek.client.ui.Messages;
+import megamek.server.Server;
+import megamek.server.commands.arguments.Argument;
+import megamek.server.commands.arguments.IntegerArgument;
+import megamek.server.totalwarfare.TWGameManager;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Luana Coppio
+ */
+public class KillCommand extends GamemasterServerCommand{
+
+ public static final String UNIT_ID = "unitID";
+
+ /** Creates new KillCommand */
+ public KillCommand(Server server, TWGameManager gameManager) {
+ super(server, gameManager, "kill", Messages.getString("Gamemaster.cmd.kill.help"),
+ Messages.getString("Gamemaster.cmd.kill.longName"));
+ }
+
+ @Override
+ public List> defineArguments() {
+ return List.of(new IntegerArgument(UNIT_ID, Messages.getString("Gamemaster.cmd.kill.unitID")));
+ }
+
+ /**
+ * Run this command with the arguments supplied
+ */
+ @Override
+ protected void runAsGM(int connId, Map> args) {
+ int unitId = (int) args.get(UNIT_ID).getValue();
+ // is the unit on the board?
+ var unit = gameManager.getGame().getEntity(unitId);
+ if (unit == null) {
+ server.sendServerChat(connId, Messages.getString("Gamemaster.cmd.missingUnit"));
+ return;
+ }
+ gameManager.destroyEntity(unit, Messages.getString("Gamemaster.cmd.kill.reason"), false, false);
+ server.sendServerChat(unit.getDisplayName() + Messages.getString("Gamemaster.cmd.kill.success"));
+ }
+}
diff --git a/megamek/src/megamek/server/commands/NoFiresCommand.java b/megamek/src/megamek/server/commands/NoFiresCommand.java
new file mode 100644
index 00000000000..f097ff1fbb2
--- /dev/null
+++ b/megamek/src/megamek/server/commands/NoFiresCommand.java
@@ -0,0 +1,87 @@
+/*
+ * MegaMek - Copyright (c) 2024 - The MegaMek Team. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+package megamek.server.commands;
+
+import megamek.client.ui.Messages;
+import megamek.common.Coords;
+import megamek.common.Hex;
+import megamek.server.Server;
+import megamek.server.commands.arguments.Argument;
+import megamek.server.commands.arguments.IntegerArgument;
+import megamek.server.totalwarfare.TWGameManager;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The Server Command "/nofires" removes all fires on the board.
+ *
+ * @author Luana Coppio
+ */
+public class NoFiresCommand extends GamemasterServerCommand {
+
+ private final String reason;
+
+ public NoFiresCommand(Server server, TWGameManager gameManager) {
+ super(server,
+ gameManager,
+ "nofires",
+ Messages.getString("Gamemaster.cmd.nofire.help"),
+ Messages.getString("Gamemaster.cmd.nofire.longName"));
+ this.reason = Messages.getString("Gamemaster.cmd.firefight.reason");
+ }
+
+ @Override
+ public List> defineArguments() {
+ return List.of();
+ }
+
+ /**
+ * Run this command with the arguments supplied
+ *
+ * @see ServerCommand#run(int, String[])
+ */
+ @Override
+ protected void runAsGM(int connId, Map> args) {
+ try {
+ getAllCoords().forEach(this::firefight);
+ } catch (Exception e) {
+ logger.error(Messages.getString("Gamemaster.cmd.fire.failed"), e);
+ server.sendServerChat(connId, Messages.getString("Gamemaster.cmd.fire.failed"));
+ }
+ }
+
+ private HashSet getAllCoords() {
+ var boardHeight = gameManager.getGame().getBoard().getHeight();
+ var boardWidth = gameManager.getGame().getBoard().getWidth();
+ var coordsSet = new HashSet();
+ for (int x = 0; x < boardWidth; x++) {
+ for (int y = 0; y < boardHeight; y++) {
+ coordsSet.add(new Coords(x, y));
+ }
+ }
+ return coordsSet;
+ }
+
+ private void firefight(Coords coords) {
+ Hex hex = gameManager.getGame().getBoard().getHex(coords);
+ if (null == hex) {
+ // Just ignore null hexes...
+ // they should not happen, but I don't want to crash the command
+ return;
+ }
+ gameManager.removeFire(coords, reason);
+ }
+}
diff --git a/megamek/src/megamek/server/commands/OrbitalBombardmentCommand.java b/megamek/src/megamek/server/commands/OrbitalBombardmentCommand.java
new file mode 100644
index 00000000000..450338e312a
--- /dev/null
+++ b/megamek/src/megamek/server/commands/OrbitalBombardmentCommand.java
@@ -0,0 +1,74 @@
+/*
+ * MegaMek - Copyright (c) 2024 - The MegaMek Team. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+package megamek.server.commands;
+
+import megamek.client.ui.Messages;
+import megamek.server.Server;
+import megamek.server.commands.arguments.Argument;
+import megamek.server.commands.arguments.IntegerArgument;
+import megamek.server.props.OrbitalBombardment;
+import megamek.server.totalwarfare.TWGameManager;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Luana Coppio
+ */
+public class OrbitalBombardmentCommand extends GamemasterServerCommand {
+
+ public static final String X = "x";
+ public static final String Y = "y";
+ public static final String DMG = "dmg";
+ public static final String RADIUS = "radius";
+
+ public OrbitalBombardmentCommand(Server server, TWGameManager gameManager) {
+ super(server, gameManager, "ob", Messages.getString("Gamemaster.cmd.orbitalbombardment.help"),
+ Messages.getString("Gamemaster.cmd.orbitalbombardment.longName"));
+ }
+
+ @Override
+ public List> defineArguments() {
+ return List.of(
+ new IntegerArgument(X, Messages.getString("Gamemaster.cmd.x")),
+ new IntegerArgument(Y, Messages.getString("Gamemaster.cmd.y")),
+ new IntegerArgument(DMG, Messages.getString("Gamemaster.cmd.orbitalbombardment.dmg"), 10, Integer.MAX_VALUE, 100),
+ new IntegerArgument(RADIUS, Messages.getString("Gamemaster.cmd.orbitalbombardment.radius"), 1, 10, 4));
+ }
+
+ /**
+ * Run this command with the arguments supplied
+ */
+ @Override
+ protected void runAsGM(int connId, Map> args) {
+
+ var orbitalBombardmentBuilder = new OrbitalBombardment.Builder();
+
+ orbitalBombardmentBuilder.x((int) args.get(X).getValue() - 1)
+ .y((int) args.get(Y).getValue() - 1)
+ .radius((int) args.get(RADIUS).getValue())
+ .damage((int) args.get(DMG).getValue());
+ var orbitalBombardment = orbitalBombardmentBuilder.build();
+
+ // is the hex on the board?
+ if (!gameManager.getGame().getBoard().contains(orbitalBombardment.getX(), orbitalBombardment.getY())) {
+ server.sendServerChat(connId, Messages.getString("Gamemaster.cmd.orbitalbombardment.error.outofbounds"));
+ return;
+ }
+
+ gameManager.addScheduledOrbitalBombardment(orbitalBombardment);
+ server.sendServerChat(Messages.getString("Gamemaster.cmd.orbitalbombardment.success"));
+ }
+
+}
diff --git a/megamek/src/megamek/server/commands/RemoveSmokeCommand.java b/megamek/src/megamek/server/commands/RemoveSmokeCommand.java
new file mode 100644
index 00000000000..25e24540d0a
--- /dev/null
+++ b/megamek/src/megamek/server/commands/RemoveSmokeCommand.java
@@ -0,0 +1,45 @@
+/*
+ * MegaMek - Copyright (c) 2024 - The MegaMek Team. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+package megamek.server.commands;
+
+import megamek.client.ui.Messages;
+import megamek.server.Server;
+import megamek.server.commands.arguments.Argument;
+import megamek.server.totalwarfare.TWGameManager;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Luana Coppio
+ */
+public class RemoveSmokeCommand extends GamemasterServerCommand {
+
+ /** Creates new KillCommand */
+ public RemoveSmokeCommand(Server server, TWGameManager gameManager) {
+ super(server, gameManager, "nosmoke", Messages.getString("Gamemaster.cmd.removesmoke.help"),
+ Messages.getString("Gamemaster.cmd.removesmoke.longName"));
+ }
+
+ @Override
+ public List> defineArguments() {
+ return List.of();
+ }
+
+ @Override
+ protected void runAsGM(int connId, Map> args) {
+ gameManager.getSmokeCloudList().forEach(gameManager::removeSmokeTerrain);
+ server.sendServerChat(connId, Messages.getString("Gamemaster.cmd.removesmoke.success"));
+ }
+}
diff --git a/megamek/src/megamek/server/commands/RescueCommand.java b/megamek/src/megamek/server/commands/RescueCommand.java
new file mode 100644
index 00000000000..62c9df1c491
--- /dev/null
+++ b/megamek/src/megamek/server/commands/RescueCommand.java
@@ -0,0 +1,61 @@
+/*
+ * MegaMek - Copyright (c) 2024 - The MegaMek Team. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+package megamek.server.commands;
+
+import megamek.client.ui.Messages;
+import megamek.common.MovePath;
+import megamek.server.Server;
+import megamek.server.commands.arguments.Argument;
+import megamek.server.commands.arguments.IntegerArgument;
+import megamek.server.totalwarfare.TWGameManager;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author Luana Coppio
+ */
+public class RescueCommand extends GamemasterServerCommand{
+
+ public static final String UNIT_ID = "unitID";
+
+ /** Creates new Rescue Command, it "flees" a unit no matter where they are */
+ public RescueCommand(Server server, TWGameManager gameManager) {
+ super(server, gameManager, "rescue", Messages.getString("Gamemaster.cmd.rescue.help"),
+ Messages.getString("Gamemaster.cmd.rescue.longName"));
+ }
+
+ @Override
+ public List> defineArguments() {
+ return List.of(new IntegerArgument(UNIT_ID, Messages.getString("Gamemaster.cmd.rescue.unitID")));
+ }
+
+ /**
+ * Run this command with the arguments supplied
+ */
+ @Override
+ protected void runAsGM(int connId, Map> args) {
+ int unitId = (int) args.get(UNIT_ID).getValue();
+ // is the unit on the board?
+ var unit = gameManager.getGame().getEntity(unitId);
+ if (unit == null) {
+ server.sendServerChat(connId, Messages.getString("Gamemaster.cmd.missingUnit"));
+ return;
+ }
+ MovePath path = new MovePath(gameManager.getGame(), unit);
+ path.addStep(MovePath.MoveStepType.FLEE);
+ gameManager.addReport(gameManager.processLeaveMap(path, false, -1));
+ server.sendServerChat(Messages.getString("Gamemaster.cmd.rescue.success", unit.getDisplayName()));
+ }
+}
diff --git a/megamek/src/megamek/server/commands/TraitorCommand.java b/megamek/src/megamek/server/commands/TraitorCommand.java
index 6b19396f8bb..a03e977511d 100644
--- a/megamek/src/megamek/server/commands/TraitorCommand.java
+++ b/megamek/src/megamek/server/commands/TraitorCommand.java
@@ -1,20 +1,15 @@
/*
- * Copyright (c) 2021 - The MegaMek Team. All Rights Reserved.
+ * MegaMek - Copyright (c) 2024 - The MegaMek Team. All Rights Reserved.
*
- * This file is part of MegaMek.
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
*
- * MegaMek is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * MegaMek is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with MegaMek. If not, see .
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
*/
package megamek.server.commands;
@@ -25,7 +20,7 @@
/**
* The Server Command "/traitor" that will switch an entity's owner to another player.
- *
+ *
* @author Jay Lawson (Taharqa)
*/
public class TraitorCommand extends ServerCommand {
@@ -43,7 +38,7 @@ public TraitorCommand(Server server, TWGameManager gameManager) {
/**
* Run this command with the arguments supplied
- *
+ *
* @see megamek.server.commands.ServerCommand#run(int, java.lang.String[])
*/
@Override
diff --git a/megamek/src/megamek/server/commands/arguments/Argument.java b/megamek/src/megamek/server/commands/arguments/Argument.java
new file mode 100644
index 00000000000..3b77343129a
--- /dev/null
+++ b/megamek/src/megamek/server/commands/arguments/Argument.java
@@ -0,0 +1,45 @@
+package megamek.server.commands.arguments;
+
+/**
+ * Generic Argument class, can be extended for different argument types for server commands
+ * @param
+ * @author Luana Coppio
+ */
+public abstract class Argument {
+ protected T value;
+ private final String name;
+ private final String description;
+
+ /**
+ * Constructor for Generic Argument
+ * @param name name of the argument
+ * @param description description of the argument
+ */
+ public Argument(String name, String description) {
+ this.name = name;
+ this.description = description;
+ }
+
+ public T getValue() {
+ return value;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * @return the string representation of the argument
+ */
+ public String getRepr() {
+ return "<" + getName() + "=#>";
+ }
+
+ public abstract String getHelp();
+
+ public abstract void parse(String input) throws IllegalArgumentException;
+}
diff --git a/megamek/src/megamek/server/commands/arguments/EnumArgument.java b/megamek/src/megamek/server/commands/arguments/EnumArgument.java
new file mode 100644
index 00000000000..cdaae3e7b0d
--- /dev/null
+++ b/megamek/src/megamek/server/commands/arguments/EnumArgument.java
@@ -0,0 +1,61 @@
+package megamek.server.commands.arguments;
+
+import megamek.client.ui.Messages;
+
+import java.util.Arrays;
+
+/**
+ * Argument for an Enum type.
+ * @param
+ * @author Luana Coppio
+ */
+public class EnumArgument> extends Argument {
+ protected final Class enumType;
+ protected final E defaultValue;
+
+ public EnumArgument(String name, String description, Class enumType, E defaultValue) {
+ super(name, description);
+ this.enumType = enumType;
+ this.defaultValue = defaultValue;
+ }
+
+ public Class getEnumType() {
+ return enumType;
+ }
+
+ @Override
+ public void parse(String input) throws IllegalArgumentException {
+ if (input == null && defaultValue != null) {
+ value = defaultValue;
+ return;
+ } else {
+ if (input == null) {
+ throw new IllegalArgumentException(getName() + " is required.");
+ }
+ }
+ try {
+ value = Enum.valueOf(enumType, input.toUpperCase());
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException(getName() + " must be one of: "
+ + String.join(", ", Arrays.toString(enumType.getEnumConstants())));
+ }
+ }
+
+ @Override
+ public E getValue() {
+ if (value == null && defaultValue != null) {
+ return defaultValue;
+ }
+ return value;
+ }
+
+ @Override
+ public String getHelp() {
+ return getDescription() +
+ " (" + String.join(", ", Arrays.toString(enumType.getEnumConstants())) + ")" +
+ (defaultValue != null ?
+ " [default: " + defaultValue + "]. " + Messages.getString("Gamemaster.cmd.params.optional") :
+ " " + Messages.getString("Gamemaster.cmd.params.required"));
+ }
+
+}
diff --git a/megamek/src/megamek/server/commands/arguments/IntegerArgument.java b/megamek/src/megamek/server/commands/arguments/IntegerArgument.java
new file mode 100644
index 00000000000..2a942bf9e86
--- /dev/null
+++ b/megamek/src/megamek/server/commands/arguments/IntegerArgument.java
@@ -0,0 +1,78 @@
+package megamek.server.commands.arguments;
+
+import megamek.client.ui.Messages;
+
+/**
+ * Argument for an Integer type.
+ * @author Luana Coppio
+ */
+public class IntegerArgument extends Argument {
+ private final int minValue;
+ private final int maxValue;
+ private final Integer defaultValue;
+
+ public IntegerArgument(String name, String description) {
+ this(name, description, Integer.MIN_VALUE, Integer.MAX_VALUE, null);
+ }
+
+ public IntegerArgument(String name, String description, int minValue, int maxValue) {
+ this(name, description, minValue, maxValue, null);
+ }
+
+ public IntegerArgument(String name, String description, int minValue, int maxValue, Integer defaultValue) {
+ super(name, description);
+ this.minValue = minValue;
+ this.maxValue = maxValue;
+ this.defaultValue = defaultValue;
+ }
+
+ @Override
+ public Integer getValue() {
+ if (value == null && defaultValue != null) {
+ return defaultValue;
+ }
+ return value;
+ }
+
+ @Override
+ public void parse(String input) throws IllegalArgumentException {
+ if (input == null && defaultValue != null) {
+ value = defaultValue;
+ return;
+ } else {
+ if (input == null) {
+ throw new IllegalArgumentException(getName() + " is required.");
+ }
+ }
+ try {
+ int parsedValue = Integer.parseInt(input);
+ if (parsedValue < minValue || parsedValue > maxValue) {
+ throw new IllegalArgumentException(getName() + " must be between " + minValue + " and " + maxValue);
+ }
+ value = parsedValue;
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException(getName() + " must be an integer.");
+ }
+ }
+
+ public boolean hasDefaultValue() {
+ return defaultValue != null;
+ }
+
+ public int getMinValue() {
+ return minValue;
+ }
+
+ public int getMaxValue() {
+ return maxValue;
+ }
+
+ @Override
+ public String getHelp() {
+ return getDescription() + (minValue == Integer.MIN_VALUE ? "": " Min: " + minValue) +
+ (defaultValue != null ?
+ " [default: " + defaultValue + "]. " + Messages.getString("Gamemaster.cmd.params.optional") :
+ " " + Messages.getString("Gamemaster.cmd.params.required"));
+ }
+
+}
diff --git a/megamek/src/megamek/server/commands/arguments/OptionalEnumArgument.java b/megamek/src/megamek/server/commands/arguments/OptionalEnumArgument.java
new file mode 100644
index 00000000000..5e7fcd78630
--- /dev/null
+++ b/megamek/src/megamek/server/commands/arguments/OptionalEnumArgument.java
@@ -0,0 +1,57 @@
+package megamek.server.commands.arguments;
+
+import megamek.client.ui.Messages;
+
+import java.util.Arrays;
+
+/**
+ * Nullable Argument for an Enum type.
+ * @param
+ * @author Luana Coppio
+ */
+public class OptionalEnumArgument> extends EnumArgument {
+
+ public OptionalEnumArgument(String name, String description, Class enumType) {
+ super(name, description, enumType, null);
+ }
+
+ @Override
+ public void parse(String input) throws IllegalArgumentException {
+ if (input == null) {
+ return;
+ }
+ try {
+ value = enumType.getEnumConstants()[Integer.parseInt(input)];
+ } catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException(getName() + " must be one of: " + getEnumConstantsString());
+ }
+ }
+
+ public boolean isPresent() {
+ return value != null;
+ }
+
+ public boolean isEmpty() {
+ return value == null;
+ }
+
+ private String getEnumConstantsString() {
+ var sb = new StringBuilder();
+ for (int i = 0; i < enumType.getEnumConstants().length; i++) {
+ sb.append(i).append(": ").append(enumType.getEnumConstants()[i]);
+ if (i < enumType.getEnumConstants().length - 1) {
+ sb.append(", ");
+ }
+ }
+ return sb.toString();
+ }
+
+ @Override
+ public String getHelp() {
+ return getDescription() +
+ " [" + getEnumConstantsString() + "] " +
+ (defaultValue != null ? " [default: " + defaultValue + "]. " : ". ") +
+ Messages.getString("Gamemaster.cmd.params.optional");
+ }
+
+}
diff --git a/megamek/src/megamek/server/commands/arguments/OptionalIntegerArgument.java b/megamek/src/megamek/server/commands/arguments/OptionalIntegerArgument.java
new file mode 100644
index 00000000000..4bc3cd17139
--- /dev/null
+++ b/megamek/src/megamek/server/commands/arguments/OptionalIntegerArgument.java
@@ -0,0 +1,78 @@
+/*
+ * MegaMek - Copyright (c) 2024 - The MegaMek Team. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+package megamek.server.commands.arguments;
+
+import megamek.client.ui.Messages;
+
+import java.util.Optional;
+
+/**
+ * Optional Argument for an Integer type.
+ * @author Luana Coppio
+ */
+public class OptionalIntegerArgument extends Argument> {
+ private final int minValue;
+ private final int maxValue;
+
+ public OptionalIntegerArgument(String name, String description) {
+ this(name, description, Integer.MIN_VALUE, Integer.MAX_VALUE);
+ }
+
+ public OptionalIntegerArgument(String name, String description, int minValue, int maxValue) {
+ super(name, description);
+ this.minValue = minValue;
+ this.maxValue = maxValue;
+ }
+
+ @Override
+ public Optional getValue() {
+ return value;
+ }
+
+ @Override
+ public void parse(String input) throws IllegalArgumentException {
+ if (input == null) {
+ value = Optional.empty();
+ return;
+ }
+ try {
+ int parsedValue = Integer.parseInt(input);
+ if (parsedValue < getMinValue() || parsedValue > getMaxValue()) {
+ throw new IllegalArgumentException(getName() + Messages.getString("Gamemaster.cmd.error.integerparse") + getMinValue() + " and " + getMaxValue());
+ }
+ value = Optional.of(parsedValue);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException(getName() + " must be an integer.");
+ }
+ }
+
+ public int getMinValue() {
+ return minValue;
+ }
+
+ public int getMaxValue() {
+ return maxValue;
+ }
+
+ @Override
+ public String getRepr() {
+ return "[" + getName() + "]";
+ }
+
+ @Override
+ public String getHelp() {
+ return getDescription() + (minValue == Integer.MIN_VALUE ? "": " Min: " + minValue) +
+ (maxValue == Integer.MAX_VALUE ? "": " Max: " + maxValue) + ". " + Messages.getString("Gamemaster.cmd.params.optional");
+ }
+}
diff --git a/megamek/src/megamek/server/props/OrbitalBombardment.java b/megamek/src/megamek/server/props/OrbitalBombardment.java
new file mode 100644
index 00000000000..c0f711f0d44
--- /dev/null
+++ b/megamek/src/megamek/server/props/OrbitalBombardment.java
@@ -0,0 +1,130 @@
+/*
+ * MegaMek - Copyright (c) 2024 - The MegaMek Team. All Rights Reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ */
+package megamek.server.props;
+
+import megamek.common.Coords;
+
+import java.util.List;
+
+/**
+ * Represents an orbital bombardment event.
+ * x and y are board positions, damageFactor is the damage at impact point times 10, and radius is the blast radius of the explosion with
+ * regular/linear damage droppoff.
+ *
+ * @author Luana Coppio
+ */
+public class OrbitalBombardment {
+
+ private final int x;
+ private final int y;
+ private final int damage;
+ private final int radius;
+ private final Coords coords;
+ /**
+ * Represents an orbital bombardment event.
+ * x and y are board positions, damageFactor is the damage at impact point times 10, and radius is the blast radius of the explosion with
+ * regular/linear damage droppoff.
+ *
+ */
+ private OrbitalBombardment(Builder builder) {
+ this.x = builder.x;
+ this.y = builder.y;
+ this.damage = builder.damage;
+ this.radius = builder.radius;
+ this.coords = new Coords(x, y);
+ }
+
+ public Coords getCoords() {
+ return coords;
+ }
+
+ public int getX() {
+ return x;
+ }
+
+ public int getY() {
+ return y;
+ }
+
+ public int getDamage() {
+ return damage;
+ }
+
+ public int getRadius() {
+ return radius;
+ }
+
+ public int getXOffset() {
+ return x - radius;
+ }
+
+ public int getYOffset() {
+ return y - radius;
+ }
+
+ public String getImageSignature(Coords boardPosition) {
+ var offsetX = boardPosition.getX() - getXOffset();
+ var offsetY = boardPosition.getY() - getYOffset();
+ var modifier = offsetX % 2 == 0 ? "" : "_odd";
+ var imageSig = String.format("col_%d_row_%d%s.png", offsetX, offsetY, modifier);
+ return imageSig;
+ }
+
+ public List getAllAffectedCoords() {
+ return coords.allAtDistanceOrLess(radius);
+ }
+
+
+ /**
+ * Builder of an orbital bombardment event.
+ * x and y are board positions, damageFactor is the damage at impact point times 10, and radius is the blast radius of the explosion with
+ * regular/linear damage droppoff.
+ *
+ */
+ public static class Builder {
+ private int x;
+ private int y;
+ private int damage = 10;
+ private int radius = 4;
+
+ public Builder x(int x) {
+ this.x = x;
+ return this;
+ }
+
+ public Builder y(int y) {
+ this.y = y;
+ return this;
+ }
+
+ public Builder damage(int damage) {
+ this.damage = damage;
+ return this;
+ }
+
+ public Builder radius(int radius) {
+ this.radius = radius;
+ return this;
+ }
+
+ /**
+ * Builds an orbital bombardment.
+ *
+ * @return an immutable instance of an orbital bombardment.
+ */
+ public OrbitalBombardment build() {
+ return new OrbitalBombardment(this);
+ }
+ }
+}
diff --git a/megamek/src/megamek/server/totalwarfare/TWGameManager.java b/megamek/src/megamek/server/totalwarfare/TWGameManager.java
index 0b452fa59b6..87a0301b436 100644
--- a/megamek/src/megamek/server/totalwarfare/TWGameManager.java
+++ b/megamek/src/megamek/server/totalwarfare/TWGameManager.java
@@ -28,6 +28,7 @@
import megamek.MMConstants;
import megamek.client.bot.princess.BehaviorSettings;
+import megamek.client.ui.Messages;
import megamek.client.ui.swing.GUIPreferences;
import megamek.client.ui.swing.tooltip.UnitToolTip;
import megamek.common.*;
@@ -73,6 +74,7 @@
import megamek.logging.MMLogger;
import megamek.server.*;
import megamek.server.commands.*;
+import megamek.server.props.OrbitalBombardment;
import megamek.server.victory.VictoryResult;
/**
@@ -101,6 +103,7 @@ public Vector getvPhaseReport() {
private final List terrainProcessors = new ArrayList<>();
private ArrayList scheduledNukes = new ArrayList<>();
+ private ArrayList scheduledOrbitalBombardment = new ArrayList<>();
/**
* Stores a set of Coords
that have changed during this phase.
@@ -187,6 +190,17 @@ public List getCommandList(Server server) {
commands.add(new CheckBVCommand(server));
commands.add(new CheckBVTeamCommand(server));
commands.add(new NukeCommand(server, this));
+ commands.add(new KillCommand(server, this));
+ commands.add(new OrbitalBombardmentCommand(server, this));
+ commands.add(new ChangeOwnershipCommand(server, this));
+ commands.add(new DisasterCommand(server, this));
+ commands.add(new FirestarterCommand(server, this));
+ commands.add(new NoFiresCommand(server, this));
+ commands.add(new FirefightCommand(server, this));
+ commands.add(new FirestormCommand(server, this));
+ commands.add(new RemoveSmokeCommand(server, this));
+ commands.add(new RescueCommand(server, this));
+ commands.add(new ChangeWeatherCommand(server, this));
commands.add(new TraitorCommand(server, this));
commands.add(new ListEntitiesCommand(server, this));
commands.add(new AssignNovaNetServerCommand(server, this));
@@ -5280,7 +5294,7 @@ Vector processCrash(Entity entity, int vel, Coords c) {
* if it can't return)
* @return Vector of turn reports.
*/
- Vector processLeaveMap(MovePath movePath, boolean flewOff, int returnable) {
+ public Vector processLeaveMap(MovePath movePath, boolean flewOff, int returnable) {
Entity entity = movePath.getEntity();
Vector vReport = new Vector<>();
Report r;
@@ -19731,7 +19745,7 @@ public Vector damageEntity(Entity te, HitData hit, int damage,
int[] damages = { (int) Math.floor(damage_orig / 10.0),
(int) Math.floor(damage_orig / 20.0) };
doExplosion(damages, false, te.getPosition(), true, vDesc, null, 5,
- te.getId(), false);
+ te.getId(), false, false);
Report.addNewline(vDesc);
r = new Report(5410, Report.PUBLIC);
r.subject = te.getId();
@@ -20013,7 +20027,7 @@ public void doFusionEngineExplosion(int engineRating, Coords position, Vector vUnits) {
int[] myDamages = { engineRating, (engineRating / 10), (engineRating / 20),
(engineRating / 40) };
- doExplosion(myDamages, true, position, false, vDesc, vUnits, 5, -1, true);
+ doExplosion(myDamages, true, position, false, vDesc, vUnits, 5, -1, true, false);
}
/**
@@ -20021,7 +20035,7 @@ public void doFusionEngineExplosion(int engineRating, Coords position, Vector vDesc,
- Vector vUnits, int excludedUnitId) {
+ Vector vUnits, int excludedUnitId, boolean canDamageVtol) {
if (degradation < 1) {
return;
}
@@ -20037,15 +20051,16 @@ public void doExplosion(int damage, int degradation, boolean autoDestroyInSameHe
myDamages[x] = myDamages[x - 1] - degradation;
}
doExplosion(myDamages, autoDestroyInSameHex, position, allowShelter, vDesc, vUnits,
- 5, excludedUnitId, false);
+ 5, excludedUnitId, false, canDamageVtol);
}
/**
* General function to cause explosions in areas.
+ * TODO Luana: Refactor this function so it is less of a mess
*/
public void doExplosion(int[] damages, boolean autoDestroyInSameHex, Coords position,
boolean allowShelter, Vector vDesc, Vector vUnits,
- int clusterAmt, int excludedUnitId, boolean engineExplosion) {
+ int clusterAmt, int excludedUnitId, boolean engineExplosion, boolean canDamageVtol) {
if (vDesc == null) {
vDesc = new Vector<>();
}
@@ -20132,9 +20147,7 @@ public void doExplosion(int[] damages, boolean autoDestroyInSameHex, Coords posi
// Now we damage people near the explosion.
List loaded = new ArrayList<>();
- for (Iterator ents = game.getEntities(); ents.hasNext();) {
- Entity entity = ents.next();
-
+ for (var entity : game.inGameTWEntities()) {
if (entitiesHit.contains(entity)) {
continue;
}
@@ -20152,12 +20165,6 @@ public void doExplosion(int[] damages, boolean autoDestroyInSameHex, Coords posi
continue;
}
- // We are going to assume that explosions are on the ground here so
- // flying entities should be unaffected
- if (entity.isAirborne()) {
- continue;
- }
-
if ((entity instanceof MekWarrior) && !((MekWarrior) entity).hasLanded()) {
// MekWarrior is still up in the air ejecting hence safe
// from this explosion.
@@ -20173,6 +20180,7 @@ public void doExplosion(int[] damages, boolean autoDestroyInSameHex, Coords posi
}
continue;
}
+
int range = position.distance(entityPos);
if (range >= damages.length) {
@@ -20180,6 +20188,21 @@ public void doExplosion(int[] damages, boolean autoDestroyInSameHex, Coords posi
continue;
}
+ // We are going to assume that explosions are on the ground here so
+ // flying entities should be unaffected, except VTOL or WiGE
+ if (entity.isAirborne() && !canDamageVtol) {
+ continue;
+ } else if (entity.isAirborne() && canDamageVtol && entity.isAirborneVTOLorWIGE()) {
+ if (entity.getElevation() > damages.length) {
+ continue;
+ }
+ if ((range + entity.getElevation()) > damages.length) {
+ continue;
+ } else {
+ range += entity.getElevation();
+ }
+ }
+
// We might need to nuke everyone in the explosion hex. If so...
if ((range == 0) && autoDestroyInSameHex) {
// Add the reports
@@ -20262,23 +20285,53 @@ public void doExplosion(int[] damages, boolean autoDestroyInSameHex, Coords posi
r.addDesc(e);
r.add(damage);
vDesc.addElement(r);
-
- while (damage > 0) {
- int cluster = Math.min(5, damage);
- int table = ToHitData.HIT_NORMAL;
- if (e instanceof ProtoMek) {
- table = ToHitData.HIT_SPECIAL_PROTO;
- }
- HitData hit = e.rollHitLocation(table, ToHitData.SIDE_FRONT);
- vDesc.addAll(damageEntity(e, hit, cluster, false,
- DamageType.IGNORE_PASSENGER, false, true));
- damage -= cluster;
+ if (canDamageVtol) {
+ orbitalBombardmentDamage(position, vDesc, e, damage);
+ } else {
+ explosionDamage(position, vDesc, e, damage);
}
Report.addNewline(vDesc);
}
}
}
+ private void explosionDamage(Coords position, Vector vDesc, Entity e, int damage) {
+ while (damage > 0) {
+ int cluster = Math.min(5, damage);
+ int table = ToHitData.HIT_NORMAL;
+ if (e instanceof ProtoMek) {
+ table = ToHitData.HIT_SPECIAL_PROTO;
+ }
+ HitData hit = e.rollHitLocation(table, ToHitData.SIDE_FRONT);
+ vDesc.addAll(damageEntity(e, hit, cluster, false,
+ DamageType.IGNORE_PASSENGER, false, true));
+ damage -= cluster;
+ }
+ }
+
+ private void orbitalBombardmentDamage(Coords position, Vector vDesc, Entity e, int damage) {
+ var distanceFromGroundZero = e.getPosition().distance(position);
+ while (damage > 0) {
+ int cluster = Math.min(5, damage);
+ int table = ToHitData.HIT_NORMAL;
+ int hitSide = ToHitData.SIDE_RANDOM;
+
+ if (e instanceof ProtoMek) {
+ table = ToHitData.HIT_SPECIAL_PROTO;
+ } else if ((e instanceof Mek) || (e instanceof Tank)) {
+ if (distanceFromGroundZero == 0) {
+ table = ToHitData.HIT_ABOVE;
+ }
+ hitSide = e.sideTable(position);
+ }
+
+ HitData hit = e.rollHitLocation(table, hitSide);
+ vDesc.addAll(damageEntity(e, hit, cluster, false,
+ DamageType.IGNORE_PASSENGER, false, true));
+ damage -= cluster;
+ }
+ }
+
/**
* Check if an Entity of the passed height can find shelter from a nuke blast
*
@@ -20336,6 +20389,97 @@ private boolean isSheltered() {
*/
public void addScheduledNuke(int[] nuke) {
scheduledNukes.add(nuke);
+ drawNukeIncomingOnBoard(nuke);
+ }
+
+ /**
+ * add an orbital bombardment to hit the board in the next weapons attack phase
+ *
+ * @param orbitalBombardment this is an #OrbitalBombardment object, its immutable and must be constructed
+ * through it's builder.
+ */
+ public void addScheduledOrbitalBombardment(OrbitalBombardment orbitalBombardment) {
+ Report r = new Report(1302);
+ r.indent()
+ .newLines(0)
+ .add(Messages.getString("OrbitalBombardment.source"))
+ .add(orbitalBombardment.getCoords().getBoardNum());
+ getvPhaseReport().addElement(r);
+ Report.addNewline(getvPhaseReport());
+
+ drawOrbitalBombardmentIncomingOnBoard(orbitalBombardment);
+ scheduledOrbitalBombardment.add(orbitalBombardment);
+ getGame().setOrbitalBombardmentVector(new Vector<>(scheduledOrbitalBombardment));
+ }
+
+ /**
+ * Draws one "orbital bombardment target" on each hex where it is going to hit.
+ * @param orbitalBombardment The orbital bombardment to be drawn.
+ */
+ private void drawOrbitalBombardmentOnBoard(OrbitalBombardment orbitalBombardment) {
+ var allCoords = orbitalBombardment.getAllAffectedCoords();
+ var hexes = getGame().getBoard().getSpecialHexDisplayTable();
+ var message = Messages.getString("OrbitalBombardment.hitOnRound", getGame().getRoundCount());
+ for (var coord : allCoords) {
+ if (!getGame().getBoard().contains(coord)) {
+ continue;
+ }
+
+ var removeIncoming = hexes.get(coord).stream()
+ .filter(sdh -> sdh.getType() == SpecialHexDisplay.Type.ORBITAL_BOMBARDMENT_INCOMING)
+ .toList();
+
+ for (var shd : removeIncoming) {
+ getGame().getBoard().removeSpecialHexDisplay(coord, shd);
+ }
+
+ if (orbitalBombardment.getRadius() == SpecialHexDisplay.LARGE_EXPLOSION_IMAGE_RADIUS) {
+ String imageSignature = orbitalBombardment.getImageSignature(coord);
+ getGame().getBoard().addSpecialHexDisplay(
+ coord,
+ new SpecialHexDisplay(
+ SpecialHexDisplay.Type.ORBITAL_BOMBARDMENT,
+ getGame().getRoundCount(),
+ getGame().getPlayersList().get(0), // The player should not matter, I just dont want to cause a nullpointererror
+ message,
+ SpecialHexDisplay.SHD_OBSCURED_ALL,
+ imageSignature));
+ } else {
+ getGame().getBoard().addSpecialHexDisplay(
+ coord,
+ new SpecialHexDisplay(
+ SpecialHexDisplay.Type.BOMB_HIT,
+ getGame().getRoundCount(),
+ getGame().getPlayersList().get(0), // The player should not matter, I just dont want to cause a nullpointererror
+ message,
+ SpecialHexDisplay.SHD_OBSCURED_ALL));
+ }
+ sendChangedHex(coord);
+ }
+ }
+
+
+ /**
+ * Draws one "orbital bombardment target" on each hex where it is going to hit.
+ * @param orbitalBombardment The orbital bombardment to be drawn.
+ */
+ private void drawOrbitalBombardmentIncomingOnBoard(OrbitalBombardment orbitalBombardment) {
+ var allCoords = orbitalBombardment.getAllAffectedCoords();
+ // var allCoords = orbitalBombardment.getCoords().allAtDistanceOrLess(SpecialHexDisplay.LARGE_EXPLOSION_IMAGE_RADIUS);
+ for (var coord : allCoords) {
+ if (!getGame().getBoard().contains(coord)) {
+ continue;
+ }
+ getGame().getBoard().addSpecialHexDisplay(
+ coord,
+ new SpecialHexDisplay(
+ SpecialHexDisplay.Type.ORBITAL_BOMBARDMENT_INCOMING,
+ getGame().getRoundCount(),
+ getGame().getPlayersList().get(0), // The player should not matter, I just dont want to cause a nullpointererror
+ Messages.getString("OrbitalBombardment.hitOnRound", getGame().getRoundCount()),
+ SpecialHexDisplay.SHD_OBSCURED_ALL));
+ sendChangedHex(coord);
+ }
}
/**
@@ -20343,6 +20487,7 @@ public void addScheduledNuke(int[] nuke) {
*/
void resolveScheduledNukes() {
for (int[] nuke : scheduledNukes) {
+ drawNukeHitOnBoard(nuke);
if (nuke.length == 3) {
doNuclearExplosion(new Coords(nuke[0] - 1, nuke[1] - 1), nuke[2],
vPhaseReport);
@@ -20355,6 +20500,85 @@ void resolveScheduledNukes() {
scheduledNukes.clear();
}
+ /**
+ * explode any scheduled orbital bombardments
+ */
+ void resolveScheduledOrbitalBombardments() {
+ if (scheduledOrbitalBombardment.isEmpty()) {
+ return;
+ }
+
+ var r = new Report(1303, Report.PUBLIC);
+ r.indent();
+ r.newlines = 2;
+ getvPhaseReport().add(r);
+
+ scheduledOrbitalBombardment
+ .forEach(ob -> doOrbitalBombardment(new Coords(ob.getX(), ob.getY()), ob.getDamage(), ob.getRadius()));
+ scheduledOrbitalBombardment.forEach(this::drawOrbitalBombardmentOnBoard);
+ scheduledOrbitalBombardment.clear();
+ getGame().resetOrbitalBombardmentAttacks();
+
+ r = new Report(1301, Report.PUBLIC);
+ r.indent();
+ r.newlines = 2;
+ getvPhaseReport().add(r);
+ }
+
+ public void drawNukeHitOnBoard(int[] nukeArgs) {
+ // Turns out this object can be used here
+ var nuke = new OrbitalBombardment.Builder().x(nukeArgs[0] - 1).y(nukeArgs[1] -1).radius(4).damage(0).build();
+
+ var allCoords = nuke.getAllAffectedCoords();
+ var hexes = getGame().getBoard().getSpecialHexDisplayTable();
+
+ for (var coord : allCoords) {
+ if (!getGame().getBoard().contains(coord)) {
+ continue;
+ }
+
+ var removeIncoming = hexes.get(coord).stream()
+ .filter(sdh -> sdh.getType() == SpecialHexDisplay.Type.NUKE_INCOMING)
+ .toList();
+
+ for (var shd : removeIncoming) {
+ getGame().getBoard().removeSpecialHexDisplay(coord, shd);
+ }
+
+ String imageSignature = nuke.getImageSignature(coord);
+ getGame().getBoard().addSpecialHexDisplay(
+ coord,
+ new SpecialHexDisplay(
+ SpecialHexDisplay.Type.NUKE_HIT,
+ getGame().getRoundCount(),
+ getGame().getPlayersList().get(0), // The player should not matter, I just dont want to cause a nullpointererror
+ Messages.getString("Nuke.exploded"),
+ SpecialHexDisplay.SHD_OBSCURED_ALL,
+ imageSignature));
+ sendChangedHex(coord);
+ }
+ }
+
+ private void drawNukeIncomingOnBoard(int[] nukeArgs) {
+ var nuke = new OrbitalBombardment.Builder().x(nukeArgs[0] - 1).y(nukeArgs[1] -1).radius(4).damage(0).build();
+
+ var allCoords = nuke.getAllAffectedCoords();
+ for (var coord : allCoords) {
+ if (!getGame().getBoard().contains(coord)) {
+ continue;
+ }
+ getGame().getBoard().addSpecialHexDisplay(
+ coord,
+ new SpecialHexDisplay(
+ SpecialHexDisplay.Type.NUKE_INCOMING,
+ getGame().getRoundCount(),
+ getGame().getPlayersList().get(0), // The player should not matter, I just dont want to cause a nullpointererror
+ Messages.getString("Nuke.hitOnRound", getGame().getRoundCount()),
+ SpecialHexDisplay.SHD_OBSCURED_ALL));
+ sendChangedHex(coord);
+ }
+ }
+
/**
* do a nuclear explosion
*
@@ -20373,6 +20597,30 @@ public void doNuclearExplosion(Coords position, int nukeType, Vector vDe
nukeStats.craterDepth, vDesc);
}
+ /**
+ * do an orbital bombardment
+ * @param position the position that will be hit by the orbital bombardment
+ * @param damage the damage of the bombardment at target hex
+ * @param radius the radius which the damage will hit
+ */
+ public void doOrbitalBombardment(Coords position, int damage, int radius) {
+ Report r = new Report(1300, Report.PUBLIC);
+ r.indent();
+ r.add(position.getBoardNum(), true);
+ getvPhaseReport().add(r);
+
+ // Then, do actual blast damage.
+ // Use the standard blast function for this.
+ Vector tmpV = new Vector<>();
+ Vector blastedUnitsVec = new Vector<>();
+ int range = radius + 1;
+ var degradation = damage / range;
+ doExplosion(damage, degradation , false, position, true, tmpV,
+ blastedUnitsVec, -1, true);
+ Report.indentAll(tmpV, 2);
+ getvPhaseReport().addAll(tmpV);
+ }
+
/**
* explode a nuke
*
@@ -20467,8 +20715,9 @@ public void doNuclearExplosion(Coords position, int baseDamage, int degradation,
// Use the standard blast function for this.
Vector tmpV = new Vector<>();
Vector blastedUnitsVec = new Vector<>();
+
doExplosion(baseDamage, degradation, true, position, true, tmpV,
- blastedUnitsVec, -1);
+ blastedUnitsVec, -1, false);
Report.indentAll(tmpV, 2);
vDesc.addAll(tmpV);
@@ -24381,7 +24630,7 @@ public Vector explodeEquipment(Entity en, int loc, Mounted> mounted, b
// attached to
if ((mounted.getType() instanceof MiscType) && mounted.getType().hasFlag(MiscType.F_RISC_LASER_PULSE_MODULE)) {
hit.setEffect(HitData.EFFECT_NO_CRITICALS);
- Mounted> laser = mounted.getLinkedBy();
+ Mounted> laser = mounted.getLinked();
if (en instanceof Mek) {
for (int slot = 0; slot < en.getNumberOfCriticals(laser.getLocation()); slot++) {
CriticalSlot cs = en.getCritical(laser.getLocation(), slot);
@@ -26740,9 +26989,11 @@ public boolean accept(Entity entity) {
&& (lastUnitNum == entity.getUnitNumber());
}
});
+ if(lastUnit.next() != null){
Entity lastUnitMember = lastUnit.next();
lastUnitMember.setUnitNumber(deletedUnitNum);
entityUpdate(lastUnitMember.getId());
+ }
} // End update-unit-number
} // End added-ProtoMek
@@ -28369,7 +28620,7 @@ public Vector damageBuilding(Building bldg, int damage, String why, Coor
Vector vRep = new Vector<>();
doExplosion(((FuelTank) bldg).getMagnitude(), 10,
false, bldg.getCoords().nextElement(), true,
- vRep, null, -1);
+ vRep, null, -1, false);
Report.indentAll(vRep, 2);
vPhaseReport.addAll(vRep);
return vPhaseReport;
@@ -28566,7 +28817,7 @@ private Vector criticalGunEmplacement(Vector guns, Build
Vector vRep = new Vector<>();
doExplosion(((FuelTank) bldg).getMagnitude(), 10, false,
bldg.getCoords().nextElement(), true, vRep, null,
- -1);
+ -1, false);
Report.indentAll(vRep, 2);
vDesc.addAll(vRep);
return vPhaseReport;
diff --git a/megamek/src/megamek/server/totalwarfare/TWPhaseEndManager.java b/megamek/src/megamek/server/totalwarfare/TWPhaseEndManager.java
index fb402b691a5..4edf0209754 100644
--- a/megamek/src/megamek/server/totalwarfare/TWPhaseEndManager.java
+++ b/megamek/src/megamek/server/totalwarfare/TWPhaseEndManager.java
@@ -135,6 +135,7 @@ void managePhase() {
gameManager.assignAMS();
gameManager.handleAttacks();
gameManager.resolveScheduledNukes();
+ gameManager.resolveScheduledOrbitalBombardments();
gameManager.applyBuildingDamage();
gameManager.checkForPSRFromDamage();
gameManager.cleanupDestroyedNarcPods();
diff --git a/megamek/src/megamek/utilities/BoardClassifier.java b/megamek/src/megamek/utilities/BoardClassifier.java
index 1cdfadb0b54..9ee76dac0d3 100644
--- a/megamek/src/megamek/utilities/BoardClassifier.java
+++ b/megamek/src/megamek/utilities/BoardClassifier.java
@@ -19,11 +19,7 @@
package megamek.utilities;
import java.io.File;
-import java.util.ArrayList;
-import java.util.EnumMap;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
import java.util.stream.Collectors;
import megamek.common.Board;
@@ -50,6 +46,9 @@ public class BoardClassifier {
// function that maps full board paths to partial board paths
private Map boardPaths = new HashMap<>();
+ private Map boardTags = new HashMap<>();
+ private Map boardWidth = new HashMap<>();
+ private Map boardHeight = new HashMap<>();
public Map> getBoardsByTag() {
return boardsByTag;
@@ -79,6 +78,18 @@ public Map getBoardPaths() {
return boardPaths;
}
+ public Map getBoardTags() {
+ return boardTags;
+ }
+
+ public Map getBoardWidth() {
+ return boardWidth;
+ }
+
+ public Map getBoardHeigth() {
+ return boardHeight;
+ }
+
public void setBoardPaths(Map boardPaths) {
this.boardPaths = boardPaths;
}
@@ -137,15 +148,22 @@ private void scanForBoardsInDir(final File boardDir, final String basePath) {
getBoardsByHeight().get(dimension.height()).add(filePath.getPath());
getBoardsByWidth().get(dimension.width()).add(filePath.getPath());
- }
- for (String tagString : Board.getTags(filePath)) {
- Tags tag = Tags.parse(tagString);
- getBoardsByTag().putIfAbsent(tag, new ArrayList<>());
- getBoardsByTag().get(tag).add(filePath.getPath());
- }
+ Set boardTags = Board.getTags(filePath);
- getBoardPaths().put(filePath.getPath(), partialBoardPath);
+ for (String tagString : boardTags) {
+ Tags tag = Tags.parse(tagString);
+ if (tag != null) {
+ getBoardsByTag().putIfAbsent(tag, new ArrayList<>());
+ getBoardsByTag().get(tag).add(filePath.getPath());
+ }
+ }
+
+ getBoardPaths().put(filePath.getPath(), partialBoardPath);
+ getBoardTags().put(filePath.getPath(), boardTags.toString());
+ getBoardWidth().put(filePath.getPath(), dimension.width());
+ getBoardHeigth().put(filePath.getPath(), dimension.height());
+ }
}
}
}
diff --git a/megamek/src/megamek/utilities/BoardsTagger.java b/megamek/src/megamek/utilities/BoardsTagger.java
index cb381a87a33..8fd677a5c99 100644
--- a/megamek/src/megamek/utilities/BoardsTagger.java
+++ b/megamek/src/megamek/utilities/BoardsTagger.java
@@ -21,18 +21,11 @@
import static java.util.stream.Collectors.toSet;
import static megamek.common.Terrains.*;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.EnumMap;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.io.*;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.*;
+import java.util.stream.Collectors;
import megamek.common.Board;
import megamek.common.Building;
@@ -126,8 +119,17 @@ public static Tags parse(String tag) {
public static void main(String... args) {
try {
+ Map> boardCheckSum = new HashMap<>();
+
File boardDir = Configuration.boardsDir();
- scanForBoards(boardDir);
+ scanForBoards(boardDir, boardCheckSum);
+
+ boardCheckSum.forEach((key, value) -> {
+ if (value.size() > 1) {
+ String message = key + " : " + value.stream().sorted().collect(Collectors.joining(", "));
+ logger.info(message);
+ }
+ });
} catch (Exception ex) {
logger.error(ex, "Board tagger cannot scan boards");
System.exit(64);
@@ -140,19 +142,21 @@ public static void main(String... args) {
* Recursively scans the supplied file/directory for any boards and auto-tags
* them.
*/
- private static void scanForBoards(File file) throws IOException {
+ private static void scanForBoards(File file, Map> boardCheckSum) throws IOException {
if (file.isDirectory()) {
String[] fileList = file.list();
for (String filename : fileList) {
File filepath = new File(file, filename);
if (filepath.isDirectory()) {
- scanForBoards(new File(file, filename));
+ scanForBoards(new File(file, filename), boardCheckSum);
} else {
tagBoard(filepath);
+ checkSum(boardCheckSum, filepath);
}
}
} else {
tagBoard(file);
+ checkSum(boardCheckSum, file);
}
}
@@ -347,4 +351,37 @@ private static void tagBoard(File boardFile) {
}
}
}
+
+ private static void checkSum(Map> boardCheckSum, File boardFile) {
+ MessageDigest md;
+
+ try {
+ md = MessageDigest.getInstance("SHA-256");
+
+ String line;
+ List lines = new ArrayList<>();
+
+ // remove tag lines
+ try (BufferedReader br = new BufferedReader(new FileReader(boardFile));) {
+ while ((line = br.readLine()) != null) {
+ if (!line.startsWith("tag ")) {
+ lines.add(line);
+ }
+ }
+ } catch (Exception e) {
+ logger.error(e, "Error Calculating Hash");
+ }
+
+ String sortedLines = lines.stream().sorted().collect(Collectors.joining());
+
+ md.update(sortedLines.getBytes(), 0, sortedLines.length());
+ HexFormat hexFormat = HexFormat.of();
+ String cs = hexFormat.formatHex(md.digest()).toUpperCase();
+ boardCheckSum.putIfAbsent(cs, new ArrayList<>());
+
+ boardCheckSum.get(cs).add(boardFile.getPath());
+ } catch (NoSuchAlgorithmException e) {
+ logger.error(e, "SHA-256 Algorithm Can't be Found");
+ }
+ }
}
diff --git a/megamek/unittests/megamek/client/bot/princess/FireControlTest.java b/megamek/unittests/megamek/client/bot/princess/FireControlTest.java
index 12eea2750ad..a5e78ac4021 100644
--- a/megamek/unittests/megamek/client/bot/princess/FireControlTest.java
+++ b/megamek/unittests/megamek/client/bot/princess/FireControlTest.java
@@ -1862,13 +1862,13 @@ void testGuessToHitModifierForWeapon() {
when(((Mek) mockTarget).getCockpitType()).thenReturn(Mek.COCKPIT_STANDARD);
// Test weapon mods.
- when(mockWeaponType.getToHitModifier()).thenReturn(-2);
+ when(mockWeaponType.getToHitModifier(mockWeapon)).thenReturn(-2);
expected = new ToHitData(mockShooter.getCrew().getGunnery(), FireControl.TH_GUNNERY);
expected.addModifier(FireControl.TH_MEDIUM_RANGE);
expected.addModifier(-2, FireControl.TH_WEAPON_MOD);
assertToHitDataEquals(expected, testFireControl.guessToHitModifierForWeapon(mockShooter,
mockShooterState, mockTarget, mockTargetState, mockWeapon, mockAmmo, mockGame));
- when(mockWeaponType.getToHitModifier()).thenReturn(0);
+ when(mockWeaponType.getToHitModifier(mockWeapon)).thenReturn(0);
// Test heat mods.
when(mockShooter.getHeatFiringModifier()).thenReturn(1);
diff --git a/megamek/unittests/megamek/common/CoordsTest.java b/megamek/unittests/megamek/common/CoordsTest.java
index cda98984495..705de690d0a 100644
--- a/megamek/unittests/megamek/common/CoordsTest.java
+++ b/megamek/unittests/megamek/common/CoordsTest.java
@@ -83,6 +83,20 @@ void testAdjacent() {
new Coords(0, 0).allAtDistance(2).forEach(coords -> assertTrue(expectedAtDistance2.contains(coords)));
}
+ @Test
+ void testAllAtDIstance() {
+ assertEquals(new Coords(10, 10).allAtDistanceOrLess(1).size(), 7);
+ assertEquals(new Coords(10, 10).allLessThanDistance(1).size(), 1);
+ assertEquals(new Coords(10, 10).allAtDistanceOrLess(0).size(), 1);
+ }
+
+ @Test
+ void testTranslation() {
+ Coords center = new Coords(8, 9);
+ assertEquals(new Coords(7, 9), center.translated(4, 1));
+ assertEquals(new Coords(7, 8), center.translated(5, 1));
+ }
+
@Test
void testHexRow() {
Coords center = new Coords(3, 7);