From 8365400ff5cc12a03c8db07b188963534b7e1ea7 Mon Sep 17 00:00:00 2001 From: Julien Ganichot <2302338+Ganitzsh@users.noreply.github.com> Date: Mon, 2 Nov 2020 04:43:23 +0100 Subject: [PATCH] Feat(KeyMapping): Adds a configurable keymap to GameControls, resolves #793 (#893) * Feat(KeyMapping): Adds a configurable keymap to GameControls + Updates help overlay to use it Co-authored-by: gravestench --- d2common/d2enum/game_event.go | 70 ++++ d2common/d2enum/input_key.go | 328 +++++++----------- d2game/d2player/game_controls.go | 55 ++- .../{help/help.go => help_overlay.go} | 73 ++-- d2game/d2player/key_map.go | 139 ++++++++ 5 files changed, 418 insertions(+), 247 deletions(-) create mode 100644 d2common/d2enum/game_event.go rename d2game/d2player/{help/help.go => help_overlay.go} (89%) create mode 100644 d2game/d2player/key_map.go diff --git a/d2common/d2enum/game_event.go b/d2common/d2enum/game_event.go new file mode 100644 index 00000000..9b197ec5 --- /dev/null +++ b/d2common/d2enum/game_event.go @@ -0,0 +1,70 @@ +package d2enum + +// GameEvent represents an envent in the game engine +type GameEvent int + +// Game events +const ( + // ToggleGameMenu will display the game menu + ToggleGameMenu GameEvent = iota + 1 + + // panel toggles + ToggleCharacterPanel + ToggleInventoryPanel + TogglePartyPanel + ToggleSkillTreePanel + ToggleHirelingPanel + ToggleQuestLog + ToggleHelpScreen + ToggleChatOverlay + ToggleMessageLog + ToggleRightSkillSelector // these two are for left/right speed-skill panel toggles + ToggleLeftSkillSelector + + ToggleAutomap + CenterAutomap // recenters the automap when opened + FadeAutomap // reduces the brightness of the map (not the players/npcs) + TogglePartyOnAutomap // toggles the display of the party members on the automap + ToggleNamesOnAutomap // toggles the display of party members names and npcs on the automap + + // there can be 16 hotkeys, each hotkey can have a skill assigned + UseSkill1 + UseSkill2 + UseSkill3 + UseSkill4 + UseSkill5 + UseSkill6 + UseSkill7 + UseSkill8 + UseSkill9 + UseSkill10 + UseSkill11 + UseSkill12 + UseSkill13 + UseSkill14 + UseSkill15 + UseSkill16 + + // switching between prev/next skill + SelectPreviousSkill + SelectNextSkill + + // ToggleBelts toggles the display of the different level for + // the currently equipped belt + ToggleBelts + UseBeltSlot1 + UseBeltSlot2 + UseBeltSlot3 + UseBeltSlot4 + + SwapWeapons + ToggleRunWalk + + // these events are fired while a player holds the corresponding key + HoldRun + HoldStandStill + HoldShowGroundItems + HoldShowPortraits + + ClearScreen // closes all active menus/panels +) diff --git a/d2common/d2enum/input_key.go b/d2common/d2enum/input_key.go index b90bf07c..92f1e2d9 100644 --- a/d2common/d2enum/input_key.go +++ b/d2common/d2enum/input_key.go @@ -3,211 +3,133 @@ package d2enum // Key represents button on a traditional keyboard. type Key int -const ( - // Key0 is the number 0 - Key0 Key = iota - // Key1 is the number 1 - Key1 - // Key2 is the number 2 - Key2 - // Key3 is the number 3 - Key3 - // Key4 is the number 4 - Key4 - // Key5 is the number 5 - Key5 - // Key6 is the number 6 - Key6 - // Key7 is the number 7 - Key7 - // Key8 is the number 8 - Key8 - // Key9 is the number 9 - Key9 - // KeyA is the letter A - KeyA - // KeyB is the letter B - KeyB - // KeyC is the letter C - KeyC - // KeyD is the letter D - KeyD - // KeyE is the letter E - KeyE - // KeyF is the letter F - KeyF - // KeyG is the letter G - KeyG - // KeyH is the letter H - KeyH - // KeyI is the letter I - KeyI - // KeyJ is the letter J - KeyJ - // KeyK is the letter K - KeyK - // KeyL is the letter L - KeyL - // KeyM is the letter M - KeyM - // KeyN is the letter N - KeyN - // KeyO is the letter O - KeyO - // KeyP is the letter P - KeyP - // KeyQ is the letter Q - KeyQ - // KeyR is the letter R - KeyR - // KeyS is the letter S - KeyS - // KeyT is the letter T - KeyT - // KeyU is the letter U - KeyU - // KeyV is the letter V - KeyV - // KeyW is the letter W - KeyW - // KeyX is the letter X - KeyX - // KeyY is the letter Y - KeyY - // KeyZ is the letter Z - KeyZ - // KeyApostrophe is the Apostrophe - KeyApostrophe - // KeyBackslash is the Backslash - KeyBackslash - // KeyBackspace is the Backspace - KeyBackspace - // KeyCapsLock is the CapsLock - KeyCapsLock - // KeyComma is the Comma - KeyComma - // KeyDelete is the Delete - KeyDelete - // KeyDown is the down arrow key - KeyDown - // KeyEnd is the End - KeyEnd - // KeyEnter is the Enter - KeyEnter - // KeyEqual is the Equal - KeyEqual - // KeyEscape is the Escape - KeyEscape - // KeyF1 is the function F1 - KeyF1 - // KeyF2 is the function F2 - KeyF2 - // KeyF3 is the function F3 - KeyF3 - // KeyF4 is the function F4 - KeyF4 - // KeyF5 is the function F5 - KeyF5 - // KeyF6 is the function F6 - KeyF6 - // KeyF7 is the function F7 - KeyF7 - // KeyF8 is the function F8 - KeyF8 - // KeyF9 is the function F9 - KeyF9 - // KeyF10 is the function F10 - KeyF10 - // KeyF11 is the function F11 - KeyF11 - // KeyF12 is the function F12 - KeyF12 - // KeyGraveAccent is the Grave Accent - KeyGraveAccent - // KeyHome is the home key - KeyHome - // KeyInsert is the insert key - KeyInsert - // KeyKP0 is keypad 0 - KeyKP0 - // KeyKP1 is keypad 1 - KeyKP1 - // KeyKP2 is keypad 2 - KeyKP2 - // KeyKP3 is keypad 3 - KeyKP3 - // KeyKP4 is keypad 4 - KeyKP4 - // KeyKP5 is keypad 5 - KeyKP5 - // KeyKP6 is keypad 6 - KeyKP6 - // KeyKP7 is keypad 7 - KeyKP7 - // KeyKP8 is keypad 8 - KeyKP8 - // KeyKP9 is keypad 9 - KeyKP9 - // KeyKPAdd is keypad Add - KeyKPAdd - // KeyKPDecimal is keypad Decimal - KeyKPDecimal - // KeyKPDivide is keypad Divide - KeyKPDivide - // KeyKPEnter is keypad Enter - KeyKPEnter - // KeyKPEqual is keypad Equal - KeyKPEqual - // KeyKPMultiply is keypad Multiply - KeyKPMultiply - // KeyKPSubtract is keypad Subtract - KeyKPSubtract - // KeyLeft is the left arrow key - KeyLeft - // KeyLeftBracket is the left bracket - KeyLeftBracket - // KeyMenu is the Menu key - KeyMenu - // KeyMinus is the Minus key - KeyMinus - // KeyNumLock is the NumLock key - KeyNumLock - // KeyPageDown is the PageDown key - KeyPageDown - // KeyPageUp is the PageUp key - KeyPageUp - // KeyPause is the Pause key - KeyPause - // KeyPeriod is the Period key - KeyPeriod - // KeyPrintScreen is the PrintScreen key - KeyPrintScreen - // KeyRight is the right arrow key - KeyRight - // KeyRightBracket is the right bracket key - KeyRightBracket - // KeyScrollLock is the scroll lock key - KeyScrollLock - // KeySemicolon is the semicolon key - KeySemicolon - // KeySlash is the front slash key - KeySlash - // KeySpace is the space key - KeySpace - // KeyTab is the tab key - KeyTab - // KeyUp is the up arrow key - KeyUp - // KeyAlt is the alt key - KeyAlt - // KeyControl is the control key - KeyControl - // KeyShift is the shift key - KeyShift +// GetString returns a string representing the key +func (k Key) GetString() string { + switch k { + case -1: + return "None" + case KeyControl: + return "Ctrl" + case KeyShift: + return "Shift" + case KeySpace: + return "Space" + case KeyAlt: + return "Alt" + case KeyTab: + return "Tab" + case KeyH: + return "H" + default: + return "Unknown" + } +} + +// Input keys +const ( + Key0 Key = iota + Key1 + Key2 + Key3 + Key4 + Key5 + Key6 + Key7 + Key8 + Key9 + KeyA + KeyB + KeyC + KeyD + KeyE + KeyF + KeyG + KeyH + KeyI + KeyJ + KeyK + KeyL + KeyM + KeyN + KeyO + KeyP + KeyQ + KeyR + KeyS + KeyT + KeyU + KeyV + KeyW + KeyX + KeyY + KeyZ + KeyApostrophe + KeyBackslash + KeyBackspace + KeyCapsLock + KeyComma + KeyDelete + KeyDown + KeyEnd + KeyEnter + KeyEqual + KeyEscape + KeyF1 + KeyF2 + KeyF3 + KeyF4 + KeyF5 + KeyF6 + KeyF7 + KeyF8 + KeyF9 + KeyF10 + KeyF11 + KeyF12 + KeyGraveAccent + KeyHome + KeyInsert + KeyKP0 + KeyKP1 + KeyKP2 + KeyKP3 + KeyKP4 + KeyKP5 + KeyKP6 + KeyKP7 + KeyKP8 + KeyKP9 + KeyKPAdd + KeyKPDecimal + KeyKPDivide + KeyKPEnter + KeyKPEqual + KeyKPMultiply + KeyKPSubtract + KeyLeft + KeyLeftBracket + KeyMenu + KeyMinus + KeyNumLock + KeyPageDown + KeyPageUp + KeyPause + KeyPeriod + KeyPrintScreen + KeyRight + KeyRightBracket + KeyScrollLock + KeySemicolon + KeySlash + KeySpace + KeyTab + KeyUp + KeyAlt + KeyControl + KeyShift + KeyTilde - // KeyMin is the lowest key KeyMin = Key0 - // KeyMax is the highest key KeyMax = KeyShift ) diff --git a/d2game/d2player/game_controls.go b/d2game/d2player/game_controls.go index e4ea150e..2522349e 100644 --- a/d2game/d2player/game_controls.go +++ b/d2game/d2player/game_controls.go @@ -12,7 +12,6 @@ import ( "github.com/OpenDiablo2/OpenDiablo2/d2common/d2geom" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" - "github.com/OpenDiablo2/OpenDiablo2/d2game/d2player/help" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" @@ -235,6 +234,7 @@ const ( // GameControls represents the game's controls on the screen type GameControls struct { + keyMap *KeyMap actionableRegions []actionableRegion asset *d2asset.AssetManager renderer d2interface.Renderer // https://github.com/OpenDiablo2/OpenDiablo2/issues/798 @@ -248,7 +248,7 @@ type GameControls struct { inventory *Inventory skilltree *skillTree heroStatsPanel *HeroStatsPanel - HelpOverlay *help.Overlay + HelpOverlay *HelpOverlay miniPanel *miniPanel bottomMenuRect *d2geom.Rectangle leftMenuRect *d2geom.Rectangle @@ -352,7 +352,10 @@ func NewGameControls( return nil, err } + keyMap := getDefaultKeyMap() + gc := &GameControls{ + keyMap: keyMap, asset: asset, ui: ui, renderer: renderer, @@ -366,7 +369,7 @@ func NewGameControls( skillSelectMenu: NewSkillSelectMenu(asset, ui, hero), skilltree: newSkillTree(hero.Skills, hero.Class, asset, renderer, ui, guiManager), heroStatsPanel: NewHeroStatsPanel(asset, ui, hero.Name(), hero.Class, hero.Stats), - HelpOverlay: help.NewHelpOverlay(asset, renderer, ui, guiManager), + HelpOverlay: NewHelpOverlay(asset, renderer, ui, guiManager, keyMap), miniPanel: newMiniPanel(asset, ui, isSinglePlayer), nameLabel: hoverLabel, zoneChangeText: zoneLabel, @@ -553,21 +556,34 @@ func (g *GameControls) OnKeyRepeat(event d2interface.KeyEvent) bool { // OnKeyDown handles key presses func (g *GameControls) OnKeyDown(event d2interface.KeyEvent) bool { - switch event.Key() { - case d2enum.KeyEscape: + if event.Key() == d2enum.KeyEscape { g.onEscKey() - case d2enum.KeyI: + return true + } + + gameEvent := g.keyMap.getGameEvent(event.Key()) + + switch gameEvent { + case d2enum.ClearScreen: + g.inventory.Close() + g.skilltree.Close() + g.heroStatsPanel.Close() + g.HelpOverlay.Close() + g.updateLayout() + case d2enum.ToggleInventoryPanel: g.inventory.Toggle() g.updateLayout() - case d2enum.KeyT: + case d2enum.ToggleSkillTreePanel: g.skilltree.Toggle() g.updateLayout() - case d2enum.KeyC: + case d2enum.ToggleCharacterPanel: g.heroStatsPanel.Toggle() g.updateLayout() - case d2enum.KeyR, d2enum.KeyControl: - g.onToggleRunButton() - case d2enum.KeyH: + case d2enum.ToggleRunWalk: + g.onToggleRunButton(false) + case d2enum.HoldRun: + g.onToggleRunButton(true) + case d2enum.ToggleHelpScreen: g.HelpOverlay.Toggle() g.updateLayout() default: @@ -579,9 +595,11 @@ func (g *GameControls) OnKeyDown(event d2interface.KeyEvent) bool { // OnKeyUp handles key release func (g *GameControls) OnKeyUp(event d2interface.KeyEvent) bool { - switch event.Key() { - case d2enum.KeyControl: - g.onToggleRunButton() + gameEvent := g.keyMap.getGameEvent(event.Key()) + + switch gameEvent { + case d2enum.HoldRun: + g.onToggleRunButton(true) default: return false } @@ -833,15 +851,18 @@ func (g *GameControls) loadUIButtons() { g.runButton = g.ui.NewButton(d2ui.ButtonTypeRun, "") g.runButton.SetPosition(runButtonX, runButtonY) - g.runButton.OnActivated(func() { g.onToggleRunButton() }) + g.runButton.OnActivated(func() { g.onToggleRunButton(false) }) if g.hero.IsRunToggled() { g.runButton.Toggle() } } -func (g *GameControls) onToggleRunButton() { - g.runButton.Toggle() +func (g *GameControls) onToggleRunButton(noButton bool) { + if !noButton { + g.runButton.Toggle() + } + g.hero.ToggleRunWalk() // https://github.com/OpenDiablo2/OpenDiablo2/issues/800 diff --git a/d2game/d2player/help/help.go b/d2game/d2player/help_overlay.go similarity index 89% rename from d2game/d2player/help/help.go rename to d2game/d2player/help_overlay.go index 77ae182e..229d9064 100644 --- a/d2game/d2player/help/help.go +++ b/d2game/d2player/help_overlay.go @@ -1,11 +1,11 @@ -// Package help contains the in-game diablo2 help panel -package help +package d2player import ( "fmt" "image/color" "log" + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" @@ -157,8 +157,8 @@ const ( beltDotY = 568 ) -// Overlay represents the in-game overlay that toggles visibility when the h key is pressed -type Overlay struct { +// HelpOverlay represents the in-game overlay that toggles visibility when the h key is pressed +type HelpOverlay struct { asset *d2asset.AssetManager isOpen bool renderer d2interface.Renderer @@ -169,6 +169,7 @@ type Overlay struct { layout *d2gui.Layout closeButton *d2ui.Button guiManager *d2gui.GuiManager + keyMap *KeyMap } // NewHelpOverlay creates a new HelpOverlay instance @@ -177,35 +178,38 @@ func NewHelpOverlay( renderer d2interface.Renderer, ui *d2ui.UIManager, guiManager *d2gui.GuiManager, -) *Overlay { - h := &Overlay{ + keyMap *KeyMap, +) *HelpOverlay { + h := &HelpOverlay{ asset: asset, renderer: renderer, uiManager: ui, guiManager: guiManager, + keyMap: keyMap, } return h } // Toggle the visibility state of the overlay -func (h *Overlay) Toggle() { +func (h *HelpOverlay) Toggle() { fmt.Print("Help overlay toggled\n") if h.isOpen { - h.close() + h.Close() } else { h.open() } } -func (h *Overlay) close() { +// Close will hide the help overlay +func (h *HelpOverlay) Close() { h.isOpen = false h.closeButton.SetVisible(false) h.guiManager.SetLayout(nil) } -func (h *Overlay) open() { +func (h *HelpOverlay) open() { h.isOpen = true if h.layout == nil { h.layout = d2gui.CreateLayout(h.renderer, d2gui.PositionTypeHorizontal, h.asset) @@ -218,12 +222,12 @@ func (h *Overlay) open() { } // IsOpen returns whether or not the overlay is visible/open -func (h *Overlay) IsOpen() bool { +func (h *HelpOverlay) IsOpen() bool { return h.isOpen } // IsInRect checks if the given point is within the overlay layout rectangle -func (h *Overlay) IsInRect(px, py int) bool { +func (h *HelpOverlay) IsInRect(px, py int) bool { ww, hh := h.layout.GetSize() x, y := h.layout.GetPosition() @@ -235,14 +239,14 @@ func (h *Overlay) IsInRect(px, py int) bool { } // Load the overlay graphical assets -func (h *Overlay) Load() { +func (h *HelpOverlay) Load() { h.setupOverlayFrame() h.setupTitleAndButton() h.setupBulletedList() h.setupLabelsWithLines() } -func (h *Overlay) setupOverlayFrame() { +func (h *HelpOverlay) setupOverlayFrame() { frames := []int{ frameTopLeft, frameBottomLeft, @@ -304,7 +308,7 @@ func (h *Overlay) setupOverlayFrame() { } } -func (h *Overlay) setupTitleAndButton() { +func (h *HelpOverlay) setupTitleAndButton() { // Title text := d2tbl.TranslateString("Strhelp1") // "Diablo II Help" newLabel := h.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteSky) @@ -319,7 +323,7 @@ func (h *Overlay) setupTitleAndButton() { h.closeButton = h.uiManager.NewButton(d2ui.ButtonTypeSquareClose, "") h.closeButton.SetPosition(closeButtonX, closeButtonY) h.closeButton.SetVisible(false) - h.closeButton.OnActivated(func() { h.close() }) + h.closeButton.OnActivated(func() { h.Close() }) newLabel = h.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteSky) newLabel.SetText(d2tbl.TranslateString("strClose")) // "Close" @@ -327,23 +331,35 @@ func (h *Overlay) setupTitleAndButton() { h.text = append(h.text, newLabel) } -func (h *Overlay) setupBulletedList() { +func (h *HelpOverlay) setupBulletedList() { // Bullets // the hotkeys displayed here should be pulled from a mapping of input events to game events // https://github.com/OpenDiablo2/OpenDiablo2/issues/793 // https://github.com/OpenDiablo2/OpenDiablo2/issues/794 callouts := []struct{ text string }{ // "Ctrl" should be hotkey // "Hold Down <%s> to Run" - {text: fmt.Sprintf(d2tbl.TranslateString("StrHelp2"), "Ctrl")}, + {text: fmt.Sprintf( + d2tbl.TranslateString("StrHelp2"), + h.keyMap.GetKeysForGameEvent(d2enum.HoldRun).Primary.GetString(), + )}, // "Alt" should be hotkey // "Hold down <%s> to highlight items on the ground" - {text: fmt.Sprintf(d2tbl.TranslateString("StrHelp3"), "Alt")}, + {text: fmt.Sprintf( + d2tbl.TranslateString("StrHelp3"), + h.keyMap.GetKeysForGameEvent(d2enum.HoldShowGroundItems).Primary.GetString(), + )}, // "Shift" should be hotkey // "Hold down <%s> to attack while standing still" - {text: fmt.Sprintf(d2tbl.TranslateString("StrHelp4"), "Shift")}, + {text: fmt.Sprintf( + d2tbl.TranslateString("StrHelp4"), + h.keyMap.GetKeysForGameEvent(d2enum.HoldStandStill).Primary.GetString(), + )}, // "Tab" should be hotkey // "Hit <%s> to toggle the automap on and off" - {text: fmt.Sprintf(d2tbl.TranslateString("StrHelp5"), "Tab")}, + {text: fmt.Sprintf( + d2tbl.TranslateString("StrHelp5"), + h.keyMap.GetKeysForGameEvent(d2enum.ToggleAutomap).Primary.GetString(), + )}, // "Hit to bring up the Game Menu" {text: d2tbl.TranslateString("StrHelp6")}, @@ -355,7 +371,10 @@ func (h *Overlay) setupBulletedList() { {text: d2tbl.TranslateString("StrHelp8")}, // "H" should be hotkey, - {text: fmt.Sprintf(d2tbl.TranslateString("StrHelp8a"), "H")}, + {text: fmt.Sprintf( + d2tbl.TranslateString("StrHelp8a"), + h.keyMap.GetKeysForGameEvent(d2enum.ToggleHelpScreen).Primary.GetString(), + )}, } for idx := range callouts { @@ -372,7 +391,7 @@ func (h *Overlay) setupBulletedList() { } // nolint:funlen // can't reduce -func (h *Overlay) setupLabelsWithLines() { +func (h *HelpOverlay) setupLabelsWithLines() { h.createCallout(callout{ LabelText: d2tbl.TranslateString("strlvlup"), // "New Stats" LabelX: newStatsLabelX, @@ -533,7 +552,7 @@ type callout struct { DotY int } -func (h *Overlay) createBullet(c callout) { +func (h *HelpOverlay) createBullet(c callout) { newLabel := h.uiManager.NewLabel(d2resource.FontFormal12, d2resource.PaletteSky) newLabel.SetText(c.LabelText) newLabel.SetPosition(c.LabelX, c.LabelY) @@ -553,7 +572,7 @@ func (h *Overlay) createBullet(c callout) { h.frames = append(h.frames, newDot) } -func (h *Overlay) createLabel(c callout) { +func (h *HelpOverlay) createLabel(c callout) { newLabel := h.uiManager.NewLabel(d2resource.FontFormal12, d2resource.PaletteSky) newLabel.SetText(c.LabelText) newLabel.SetPosition(c.LabelX, c.LabelY) @@ -561,7 +580,7 @@ func (h *Overlay) createLabel(c callout) { newLabel.Alignment = d2gui.HorizontalAlignCenter } -func (h *Overlay) createCallout(c callout) { +func (h *HelpOverlay) createCallout(c callout) { newLabel := h.uiManager.NewLabel(d2resource.FontFormal12, d2resource.PaletteSky) newLabel.Color[0] = color.White newLabel.SetText(c.LabelText) @@ -596,7 +615,7 @@ func (h *Overlay) createCallout(c callout) { } // Render the overlay to the given surface -func (h *Overlay) Render(target d2interface.Surface) error { +func (h *HelpOverlay) Render(target d2interface.Surface) error { if !h.isOpen { return nil } diff --git a/d2game/d2player/key_map.go b/d2game/d2player/key_map.go new file mode 100644 index 00000000..ec29e590 --- /dev/null +++ b/d2game/d2player/key_map.go @@ -0,0 +1,139 @@ +package d2player + +import ( + "sync" + + "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" +) + +// KeyMap represents the key mappings of the game. Each game event +// can be associated to 2 different keys. A key of -1 means none +type KeyMap struct { + mutex sync.RWMutex + mapping map[d2enum.Key]d2enum.GameEvent + controls map[d2enum.GameEvent]*KeyBinding +} + +// NewKeyMap returns a new instance of a KeyMap +func NewKeyMap() *KeyMap { + return &KeyMap{ + mapping: make(map[d2enum.Key]d2enum.GameEvent), + controls: make(map[d2enum.GameEvent]*KeyBinding), + } +} + +// SetPrimaryBinding binds the first key for gameEvent +func (km *KeyMap) SetPrimaryBinding(gameEvent d2enum.GameEvent, key d2enum.Key) { + if key == d2enum.KeyEscape { + return + } + + km.mutex.Lock() + defer km.mutex.Unlock() + + if km.controls[gameEvent] == nil { + km.controls[gameEvent] = &KeyBinding{} + } + + currentKey := km.controls[gameEvent].Primary + delete(km.mapping, currentKey) + km.mapping[key] = gameEvent + + km.controls[gameEvent].Primary = key +} + +// SetSecondaryBinding binds the second key for gameEvent +func (km *KeyMap) SetSecondaryBinding(gameEvent d2enum.GameEvent, key d2enum.Key) { + if key == d2enum.KeyEscape { + return + } + + km.mutex.Lock() + defer km.mutex.Unlock() + + if km.controls[gameEvent] == nil { + km.controls[gameEvent] = &KeyBinding{} + } + + currentKey := km.controls[gameEvent].Secondary + delete(km.mapping, currentKey) + km.mapping[key] = gameEvent + + if km.controls[gameEvent].Primary == key { + km.controls[gameEvent].Primary = d2enum.Key(-1) + } + + km.controls[gameEvent].Secondary = key +} + +func (km *KeyMap) getGameEvent(key d2enum.Key) d2enum.GameEvent { + km.mutex.RLock() + defer km.mutex.RUnlock() + + return km.mapping[key] +} + +// GetKeysForGameEvent returns the bindings for a givent game event +func (km *KeyMap) GetKeysForGameEvent(gameEvent d2enum.GameEvent) *KeyBinding { + km.mutex.RLock() + defer km.mutex.RUnlock() + + return km.controls[gameEvent] +} + +// KeyBinding holds the primary and secondary keys assigned to a GameEvent +type KeyBinding struct { + Primary d2enum.Key + Secondary d2enum.Key +} + +func getDefaultKeyMap() *KeyMap { + keyMap := NewKeyMap() + + defaultControls := map[d2enum.GameEvent]KeyBinding{ + d2enum.ToggleCharacterPanel: {d2enum.KeyA, d2enum.KeyC}, + d2enum.ToggleInventoryPanel: {d2enum.KeyB, d2enum.KeyI}, + d2enum.ToggleHelpScreen: {d2enum.KeyH, -1}, + d2enum.TogglePartyPanel: {d2enum.KeyP, -1}, + d2enum.ToggleMessageLog: {d2enum.KeyM, -1}, + d2enum.ToggleQuestLog: {d2enum.KeyQ, -1}, + d2enum.ToggleChatOverlay: {d2enum.KeyEnter, -1}, + d2enum.ToggleAutomap: {d2enum.KeyTab, -1}, + d2enum.CenterAutomap: {d2enum.KeyHome, -1}, + d2enum.ToggleSkillTreePanel: {d2enum.KeyT, -1}, + d2enum.ToggleRightSkillSelector: {d2enum.KeyS, -1}, + d2enum.UseSkill1: {d2enum.KeyF1, -1}, + d2enum.UseSkill2: {d2enum.KeyF2, -1}, + d2enum.UseSkill3: {d2enum.KeyF3, -1}, + d2enum.UseSkill4: {d2enum.KeyF4, -1}, + d2enum.UseSkill5: {d2enum.KeyF5, -1}, + d2enum.UseSkill6: {d2enum.KeyF6, -1}, + d2enum.UseSkill7: {d2enum.KeyF7, -1}, + d2enum.UseSkill8: {d2enum.KeyF8, -1}, + d2enum.UseSkill9: {-1, -1}, + d2enum.UseSkill10: {-1, -1}, + d2enum.UseSkill11: {-1, -1}, + d2enum.UseSkill12: {-1, -1}, + d2enum.UseSkill13: {-1, -1}, + d2enum.UseSkill14: {-1, -1}, + d2enum.UseSkill15: {-1, -1}, + d2enum.UseSkill16: {-1, -1}, + d2enum.ToggleBelts: {d2enum.KeyTilde, -1}, + d2enum.UseBeltSlot1: {d2enum.Key1, -1}, + d2enum.UseBeltSlot2: {d2enum.Key2, -1}, + d2enum.UseBeltSlot3: {d2enum.Key3, -1}, + d2enum.UseBeltSlot4: {d2enum.Key4, -1}, + d2enum.ToggleRunWalk: {d2enum.KeyR, -1}, + d2enum.HoldRun: {d2enum.KeyControl, -1}, + d2enum.HoldShowGroundItems: {d2enum.KeyAlt, -1}, + d2enum.HoldShowPortraits: {d2enum.KeyZ, -1}, + d2enum.HoldStandStill: {d2enum.KeyShift, -1}, + d2enum.ClearScreen: {d2enum.KeySpace, -1}, + } + for gameEvent, keys := range defaultControls { + keyMap.SetPrimaryBinding(gameEvent, keys.Primary) + keyMap.SetSecondaryBinding(gameEvent, keys.Secondary) + } + + return keyMap +}