hotkeys moved to mods and checked across all enabled mods (apply refuses two hotkey options on the same key), agent briefs in notes/dev
This commit is contained in:
1 parent
1c8b766454
commit
86fe26d786
10 files changed
+203
-41
No files matched your search
@@ -0,0 +1,72 @@
|
||||
package com.eliasmueller.llamamod.mods;
|
||||
|
||||
import com.eliasmueller.llamamod.platform.Config;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Hotkey choices for cave code that polls GetAsyncKeyState. F6 (screenshot in the game's window
|
||||
* procedure) and F10 (system key) are left out; the game's own controls are DirectInput keys.
|
||||
*/
|
||||
public final class Hotkeys {
|
||||
private Hotkeys() {}
|
||||
|
||||
/** Option value → Windows virtual-key code. */
|
||||
public static final Map<String, Integer> VK = new LinkedHashMap<>();
|
||||
|
||||
static {
|
||||
int[] f = {1, 2, 3, 4, 5, 7, 8, 9, 11, 12};
|
||||
for (int n : f) {
|
||||
VK.put("F" + n, 0x6F + n);
|
||||
}
|
||||
VK.put("Insert", 0x2D);
|
||||
VK.put("Delete", 0x2E);
|
||||
VK.put("Home", 0x24);
|
||||
VK.put("End", 0x23);
|
||||
}
|
||||
|
||||
public static ModOption option(String key, String label, String description, String def) {
|
||||
ModOption.Choice[] c = VK.keySet().stream().map(k -> ModOption.of(k, k)).toArray(ModOption.Choice[]::new);
|
||||
return ModOption.choice(key, label, description, def, c);
|
||||
}
|
||||
|
||||
/** True for options created by {@link #option}. */
|
||||
public static boolean isHotkey(ModOption o) {
|
||||
return o.type() == ModOption.Type.CHOICE
|
||||
&& o.choices().stream().map(ModOption.Choice::value).toList().equals(List.copyOf(VK.keySet()));
|
||||
}
|
||||
|
||||
/**
|
||||
* One message per key that more than one hotkey option of {@code mods} uses (within one mod or
|
||||
* across mods); empty if all hotkeys are distinct.
|
||||
*/
|
||||
public static List<String> conflicts(List<Mod> mods, Config cfg) {
|
||||
Map<String, List<String>> users = new LinkedHashMap<>();
|
||||
for (Mod m : ModManager.sorted(mods)) {
|
||||
Map<String, String> values = ModOptions.resolve(m, cfg);
|
||||
for (ModOption o : m.options()) {
|
||||
if (isHotkey(o)) {
|
||||
users.computeIfAbsent(values.get(o.key()), k -> new ArrayList<>())
|
||||
.add("\"" + o.label() + "\" (" + m.name() + ")");
|
||||
}
|
||||
}
|
||||
}
|
||||
List<String> out = new ArrayList<>();
|
||||
users.forEach((key, who) -> {
|
||||
if (who.size() > 1) {
|
||||
out.add(key + " is used by " + String.join(" and ", who));
|
||||
}
|
||||
});
|
||||
return out;
|
||||
}
|
||||
|
||||
public static int vk(String name) {
|
||||
Integer v = VK.get(name);
|
||||
if (v == null) {
|
||||
throw new IllegalArgumentException("unknown hotkey " + name);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
}
|
||||
@@ -347,6 +347,11 @@ public final class ModManager {
|
||||
if (gameRunning.getAsBoolean()) {
|
||||
throw new IOException("The game is running. Please quit it before applying or restoring mods.");
|
||||
}
|
||||
List<String> clashes = Hotkeys.conflicts(mods, cfg);
|
||||
if (!clashes.isEmpty()) {
|
||||
throw new IOException("Two hotkeys share a key: " + String.join("; ", clashes)
|
||||
+ ". Please choose different keys in the mod options.");
|
||||
}
|
||||
try (DataLock lock = DataLock.acquire(paths);
|
||||
ApplyLog log = new ApplyLog(paths.logs().resolve("apply.log"))) {
|
||||
log.line("mods: " + sorted(mods).stream().map(Mod::id).toList());
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
package com.eliasmueller.llamamod.mods.freecam;
|
||||
|
||||
import com.eliasmueller.llamamod.mods.Hotkeys;
|
||||
import com.eliasmueller.llamamod.mods.Mod;
|
||||
import com.eliasmueller.llamamod.mods.ModContext;
|
||||
import com.eliasmueller.llamamod.mods.ModOption;
|
||||
import com.eliasmueller.llamamod.mods.practice.Hotkeys;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
package com.eliasmueller.llamamod.mods.practice;
|
||||
|
||||
import com.eliasmueller.llamamod.mods.ModOption;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Hotkey choices for cave code that polls GetAsyncKeyState. F6 (screenshot in the game's window
|
||||
* procedure) and F10 (system key) are left out; the game's own controls are DirectInput keys.
|
||||
*/
|
||||
public final class Hotkeys {
|
||||
private Hotkeys() {}
|
||||
|
||||
/** Option value → Windows virtual-key code. */
|
||||
public static final Map<String, Integer> VK = new LinkedHashMap<>();
|
||||
|
||||
static {
|
||||
int[] f = {1, 2, 3, 4, 5, 7, 8, 9, 11, 12};
|
||||
for (int n : f) {
|
||||
VK.put("F" + n, 0x6F + n);
|
||||
}
|
||||
VK.put("Insert", 0x2D);
|
||||
VK.put("Delete", 0x2E);
|
||||
VK.put("Home", 0x24);
|
||||
VK.put("End", 0x23);
|
||||
}
|
||||
|
||||
public static ModOption option(String key, String label, String description, String def) {
|
||||
ModOption.Choice[] c = VK.keySet().stream().map(k -> ModOption.of(k, k)).toArray(ModOption.Choice[]::new);
|
||||
return ModOption.choice(key, label, description, def, c);
|
||||
}
|
||||
|
||||
public static int vk(String name) {
|
||||
Integer v = VK.get(name);
|
||||
if (v == null) {
|
||||
throw new IllegalArgumentException("unknown hotkey " + name);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package com.eliasmueller.llamamod.mods.practice;
|
||||
|
||||
import static com.eliasmueller.llamamod.mods.LevelWads.fileName;
|
||||
|
||||
import com.eliasmueller.llamamod.mods.Hotkeys;
|
||||
import com.eliasmueller.llamamod.mods.LevelWads;
|
||||
import com.eliasmueller.llamamod.mods.Mod;
|
||||
import com.eliasmueller.llamamod.mods.ModContext;
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.eliasmueller.llamamod.mods;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
import com.eliasmueller.llamamod.mods.freecam.FreeCamMod;
|
||||
import com.eliasmueller.llamamod.mods.practice.PracticeMod;
|
||||
import com.eliasmueller.llamamod.platform.Config;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class HotkeysTest {
|
||||
private final Mod practice = new PracticeMod();
|
||||
private final Mod freecam = new FreeCamMod();
|
||||
|
||||
@Test
|
||||
void defaultsDoNotClash() {
|
||||
assertTrue(Hotkeys.conflicts(List.of(practice, freecam), new Config()).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void sameKeyAcrossModsIsReported() {
|
||||
Config cfg = new Config();
|
||||
ModOptions.set(cfg, freecam, "camKey", "F5");
|
||||
List<String> c = Hotkeys.conflicts(List.of(practice, freecam), cfg);
|
||||
assertEquals(1, c.size());
|
||||
assertTrue(c.get(0).startsWith("F5 is used by"), c.get(0));
|
||||
assertTrue(Hotkeys.conflicts(List.of(freecam), cfg).isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void sameKeyWithinOneModIsReported() {
|
||||
Config cfg = new Config();
|
||||
ModOptions.set(cfg, practice, "loadKey", "F5");
|
||||
assertEquals(1, Hotkeys.conflicts(List.of(practice), cfg).size());
|
||||
}
|
||||
|
||||
@Test
|
||||
void onlyHotkeyOptionsCount() {
|
||||
assertTrue(practice.options().stream().anyMatch(Hotkeys::isHotkey));
|
||||
assertTrue(practice.options().stream()
|
||||
.filter(o -> !o.key().endsWith("Key"))
|
||||
.noneMatch(Hotkeys::isHotkey));
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user