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 <dknuth0101@gmail.com>
This commit is contained in:
Julien Ganichot 2020-11-02 04:43:23 +01:00 committed by GitHub
parent 1f2771e8bc
commit 8365400ff5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 418 additions and 247 deletions

View File

@ -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
)

View File

@ -3,211 +3,133 @@ package d2enum
// Key represents button on a traditional keyboard. // Key represents button on a traditional keyboard.
type Key int type Key int
const ( // GetString returns a string representing the key
// Key0 is the number 0 func (k Key) GetString() string {
Key0 Key = iota switch k {
// Key1 is the number 1 case -1:
Key1 return "None"
// Key2 is the number 2 case KeyControl:
Key2 return "Ctrl"
// Key3 is the number 3 case KeyShift:
Key3 return "Shift"
// Key4 is the number 4 case KeySpace:
Key4 return "Space"
// Key5 is the number 5 case KeyAlt:
Key5 return "Alt"
// Key6 is the number 6 case KeyTab:
Key6 return "Tab"
// Key7 is the number 7 case KeyH:
Key7 return "H"
// Key8 is the number 8 default:
Key8 return "Unknown"
// Key9 is the number 9 }
Key9 }
// KeyA is the letter A
KeyA // Input keys
// KeyB is the letter B const (
KeyB Key0 Key = iota
// KeyC is the letter C Key1
KeyC Key2
// KeyD is the letter D Key3
KeyD Key4
// KeyE is the letter E Key5
KeyE Key6
// KeyF is the letter F Key7
KeyF Key8
// KeyG is the letter G Key9
KeyG KeyA
// KeyH is the letter H KeyB
KeyH KeyC
// KeyI is the letter I KeyD
KeyI KeyE
// KeyJ is the letter J KeyF
KeyJ KeyG
// KeyK is the letter K KeyH
KeyK KeyI
// KeyL is the letter L KeyJ
KeyL KeyK
// KeyM is the letter M KeyL
KeyM KeyM
// KeyN is the letter N KeyN
KeyN KeyO
// KeyO is the letter O KeyP
KeyO KeyQ
// KeyP is the letter P KeyR
KeyP KeyS
// KeyQ is the letter Q KeyT
KeyQ KeyU
// KeyR is the letter R KeyV
KeyR KeyW
// KeyS is the letter S KeyX
KeyS KeyY
// KeyT is the letter T KeyZ
KeyT KeyApostrophe
// KeyU is the letter U KeyBackslash
KeyU KeyBackspace
// KeyV is the letter V KeyCapsLock
KeyV KeyComma
// KeyW is the letter W KeyDelete
KeyW KeyDown
// KeyX is the letter X KeyEnd
KeyX KeyEnter
// KeyY is the letter Y KeyEqual
KeyY KeyEscape
// KeyZ is the letter Z KeyF1
KeyZ KeyF2
// KeyApostrophe is the Apostrophe KeyF3
KeyApostrophe KeyF4
// KeyBackslash is the Backslash KeyF5
KeyBackslash KeyF6
// KeyBackspace is the Backspace KeyF7
KeyBackspace KeyF8
// KeyCapsLock is the CapsLock KeyF9
KeyCapsLock KeyF10
// KeyComma is the Comma KeyF11
KeyComma KeyF12
// KeyDelete is the Delete KeyGraveAccent
KeyDelete KeyHome
// KeyDown is the down arrow key KeyInsert
KeyDown KeyKP0
// KeyEnd is the End KeyKP1
KeyEnd KeyKP2
// KeyEnter is the Enter KeyKP3
KeyEnter KeyKP4
// KeyEqual is the Equal KeyKP5
KeyEqual KeyKP6
// KeyEscape is the Escape KeyKP7
KeyEscape KeyKP8
// KeyF1 is the function F1 KeyKP9
KeyF1 KeyKPAdd
// KeyF2 is the function F2 KeyKPDecimal
KeyF2 KeyKPDivide
// KeyF3 is the function F3 KeyKPEnter
KeyF3 KeyKPEqual
// KeyF4 is the function F4 KeyKPMultiply
KeyF4 KeyKPSubtract
// KeyF5 is the function F5 KeyLeft
KeyF5 KeyLeftBracket
// KeyF6 is the function F6 KeyMenu
KeyF6 KeyMinus
// KeyF7 is the function F7 KeyNumLock
KeyF7 KeyPageDown
// KeyF8 is the function F8 KeyPageUp
KeyF8 KeyPause
// KeyF9 is the function F9 KeyPeriod
KeyF9 KeyPrintScreen
// KeyF10 is the function F10 KeyRight
KeyF10 KeyRightBracket
// KeyF11 is the function F11 KeyScrollLock
KeyF11 KeySemicolon
// KeyF12 is the function F12 KeySlash
KeyF12 KeySpace
// KeyGraveAccent is the Grave Accent KeyTab
KeyGraveAccent KeyUp
// KeyHome is the home key KeyAlt
KeyHome KeyControl
// KeyInsert is the insert key KeyShift
KeyInsert KeyTilde
// 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
// KeyMin is the lowest key
KeyMin = Key0 KeyMin = Key0
// KeyMax is the highest key
KeyMax = KeyShift KeyMax = KeyShift
) )

View File

@ -12,7 +12,6 @@ import (
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2geom" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2geom"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2util" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2util"
"github.com/OpenDiablo2/OpenDiablo2/d2game/d2player/help"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2math/d2vector"
@ -235,6 +234,7 @@ const (
// GameControls represents the game's controls on the screen // GameControls represents the game's controls on the screen
type GameControls struct { type GameControls struct {
keyMap *KeyMap
actionableRegions []actionableRegion actionableRegions []actionableRegion
asset *d2asset.AssetManager asset *d2asset.AssetManager
renderer d2interface.Renderer // https://github.com/OpenDiablo2/OpenDiablo2/issues/798 renderer d2interface.Renderer // https://github.com/OpenDiablo2/OpenDiablo2/issues/798
@ -248,7 +248,7 @@ type GameControls struct {
inventory *Inventory inventory *Inventory
skilltree *skillTree skilltree *skillTree
heroStatsPanel *HeroStatsPanel heroStatsPanel *HeroStatsPanel
HelpOverlay *help.Overlay HelpOverlay *HelpOverlay
miniPanel *miniPanel miniPanel *miniPanel
bottomMenuRect *d2geom.Rectangle bottomMenuRect *d2geom.Rectangle
leftMenuRect *d2geom.Rectangle leftMenuRect *d2geom.Rectangle
@ -352,7 +352,10 @@ func NewGameControls(
return nil, err return nil, err
} }
keyMap := getDefaultKeyMap()
gc := &GameControls{ gc := &GameControls{
keyMap: keyMap,
asset: asset, asset: asset,
ui: ui, ui: ui,
renderer: renderer, renderer: renderer,
@ -366,7 +369,7 @@ func NewGameControls(
skillSelectMenu: NewSkillSelectMenu(asset, ui, hero), skillSelectMenu: NewSkillSelectMenu(asset, ui, hero),
skilltree: newSkillTree(hero.Skills, hero.Class, asset, renderer, ui, guiManager), skilltree: newSkillTree(hero.Skills, hero.Class, asset, renderer, ui, guiManager),
heroStatsPanel: NewHeroStatsPanel(asset, ui, hero.Name(), hero.Class, hero.Stats), 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), miniPanel: newMiniPanel(asset, ui, isSinglePlayer),
nameLabel: hoverLabel, nameLabel: hoverLabel,
zoneChangeText: zoneLabel, zoneChangeText: zoneLabel,
@ -553,21 +556,34 @@ func (g *GameControls) OnKeyRepeat(event d2interface.KeyEvent) bool {
// OnKeyDown handles key presses // OnKeyDown handles key presses
func (g *GameControls) OnKeyDown(event d2interface.KeyEvent) bool { func (g *GameControls) OnKeyDown(event d2interface.KeyEvent) bool {
switch event.Key() { if event.Key() == d2enum.KeyEscape {
case d2enum.KeyEscape:
g.onEscKey() 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.inventory.Toggle()
g.updateLayout() g.updateLayout()
case d2enum.KeyT: case d2enum.ToggleSkillTreePanel:
g.skilltree.Toggle() g.skilltree.Toggle()
g.updateLayout() g.updateLayout()
case d2enum.KeyC: case d2enum.ToggleCharacterPanel:
g.heroStatsPanel.Toggle() g.heroStatsPanel.Toggle()
g.updateLayout() g.updateLayout()
case d2enum.KeyR, d2enum.KeyControl: case d2enum.ToggleRunWalk:
g.onToggleRunButton() g.onToggleRunButton(false)
case d2enum.KeyH: case d2enum.HoldRun:
g.onToggleRunButton(true)
case d2enum.ToggleHelpScreen:
g.HelpOverlay.Toggle() g.HelpOverlay.Toggle()
g.updateLayout() g.updateLayout()
default: default:
@ -579,9 +595,11 @@ func (g *GameControls) OnKeyDown(event d2interface.KeyEvent) bool {
// OnKeyUp handles key release // OnKeyUp handles key release
func (g *GameControls) OnKeyUp(event d2interface.KeyEvent) bool { func (g *GameControls) OnKeyUp(event d2interface.KeyEvent) bool {
switch event.Key() { gameEvent := g.keyMap.getGameEvent(event.Key())
case d2enum.KeyControl:
g.onToggleRunButton() switch gameEvent {
case d2enum.HoldRun:
g.onToggleRunButton(true)
default: default:
return false return false
} }
@ -833,15 +851,18 @@ func (g *GameControls) loadUIButtons() {
g.runButton = g.ui.NewButton(d2ui.ButtonTypeRun, "") g.runButton = g.ui.NewButton(d2ui.ButtonTypeRun, "")
g.runButton.SetPosition(runButtonX, runButtonY) g.runButton.SetPosition(runButtonX, runButtonY)
g.runButton.OnActivated(func() { g.onToggleRunButton() }) g.runButton.OnActivated(func() { g.onToggleRunButton(false) })
if g.hero.IsRunToggled() { if g.hero.IsRunToggled() {
g.runButton.Toggle() g.runButton.Toggle()
} }
} }
func (g *GameControls) onToggleRunButton() { func (g *GameControls) onToggleRunButton(noButton bool) {
g.runButton.Toggle() if !noButton {
g.runButton.Toggle()
}
g.hero.ToggleRunWalk() g.hero.ToggleRunWalk()
// https://github.com/OpenDiablo2/OpenDiablo2/issues/800 // https://github.com/OpenDiablo2/OpenDiablo2/issues/800

View File

@ -1,11 +1,11 @@
// Package help contains the in-game diablo2 help panel package d2player
package help
import ( import (
"fmt" "fmt"
"image/color" "image/color"
"log" "log"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2enum"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2fileformats/d2tbl"
"github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource" "github.com/OpenDiablo2/OpenDiablo2/d2common/d2resource"
@ -157,8 +157,8 @@ const (
beltDotY = 568 beltDotY = 568
) )
// Overlay represents the in-game overlay that toggles visibility when the h key is pressed // HelpOverlay represents the in-game overlay that toggles visibility when the h key is pressed
type Overlay struct { type HelpOverlay struct {
asset *d2asset.AssetManager asset *d2asset.AssetManager
isOpen bool isOpen bool
renderer d2interface.Renderer renderer d2interface.Renderer
@ -169,6 +169,7 @@ type Overlay struct {
layout *d2gui.Layout layout *d2gui.Layout
closeButton *d2ui.Button closeButton *d2ui.Button
guiManager *d2gui.GuiManager guiManager *d2gui.GuiManager
keyMap *KeyMap
} }
// NewHelpOverlay creates a new HelpOverlay instance // NewHelpOverlay creates a new HelpOverlay instance
@ -177,35 +178,38 @@ func NewHelpOverlay(
renderer d2interface.Renderer, renderer d2interface.Renderer,
ui *d2ui.UIManager, ui *d2ui.UIManager,
guiManager *d2gui.GuiManager, guiManager *d2gui.GuiManager,
) *Overlay { keyMap *KeyMap,
h := &Overlay{ ) *HelpOverlay {
h := &HelpOverlay{
asset: asset, asset: asset,
renderer: renderer, renderer: renderer,
uiManager: ui, uiManager: ui,
guiManager: guiManager, guiManager: guiManager,
keyMap: keyMap,
} }
return h return h
} }
// Toggle the visibility state of the overlay // Toggle the visibility state of the overlay
func (h *Overlay) Toggle() { func (h *HelpOverlay) Toggle() {
fmt.Print("Help overlay toggled\n") fmt.Print("Help overlay toggled\n")
if h.isOpen { if h.isOpen {
h.close() h.Close()
} else { } else {
h.open() h.open()
} }
} }
func (h *Overlay) close() { // Close will hide the help overlay
func (h *HelpOverlay) Close() {
h.isOpen = false h.isOpen = false
h.closeButton.SetVisible(false) h.closeButton.SetVisible(false)
h.guiManager.SetLayout(nil) h.guiManager.SetLayout(nil)
} }
func (h *Overlay) open() { func (h *HelpOverlay) open() {
h.isOpen = true h.isOpen = true
if h.layout == nil { if h.layout == nil {
h.layout = d2gui.CreateLayout(h.renderer, d2gui.PositionTypeHorizontal, h.asset) 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 // IsOpen returns whether or not the overlay is visible/open
func (h *Overlay) IsOpen() bool { func (h *HelpOverlay) IsOpen() bool {
return h.isOpen return h.isOpen
} }
// IsInRect checks if the given point is within the overlay layout rectangle // 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() ww, hh := h.layout.GetSize()
x, y := h.layout.GetPosition() x, y := h.layout.GetPosition()
@ -235,14 +239,14 @@ func (h *Overlay) IsInRect(px, py int) bool {
} }
// Load the overlay graphical assets // Load the overlay graphical assets
func (h *Overlay) Load() { func (h *HelpOverlay) Load() {
h.setupOverlayFrame() h.setupOverlayFrame()
h.setupTitleAndButton() h.setupTitleAndButton()
h.setupBulletedList() h.setupBulletedList()
h.setupLabelsWithLines() h.setupLabelsWithLines()
} }
func (h *Overlay) setupOverlayFrame() { func (h *HelpOverlay) setupOverlayFrame() {
frames := []int{ frames := []int{
frameTopLeft, frameTopLeft,
frameBottomLeft, frameBottomLeft,
@ -304,7 +308,7 @@ func (h *Overlay) setupOverlayFrame() {
} }
} }
func (h *Overlay) setupTitleAndButton() { func (h *HelpOverlay) setupTitleAndButton() {
// Title // Title
text := d2tbl.TranslateString("Strhelp1") // "Diablo II Help" text := d2tbl.TranslateString("Strhelp1") // "Diablo II Help"
newLabel := h.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteSky) 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 = h.uiManager.NewButton(d2ui.ButtonTypeSquareClose, "")
h.closeButton.SetPosition(closeButtonX, closeButtonY) h.closeButton.SetPosition(closeButtonX, closeButtonY)
h.closeButton.SetVisible(false) 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 = h.uiManager.NewLabel(d2resource.Font16, d2resource.PaletteSky)
newLabel.SetText(d2tbl.TranslateString("strClose")) // "Close" newLabel.SetText(d2tbl.TranslateString("strClose")) // "Close"
@ -327,23 +331,35 @@ func (h *Overlay) setupTitleAndButton() {
h.text = append(h.text, newLabel) h.text = append(h.text, newLabel)
} }
func (h *Overlay) setupBulletedList() { func (h *HelpOverlay) setupBulletedList() {
// Bullets // Bullets
// the hotkeys displayed here should be pulled from a mapping of input events to game events // 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/793
// https://github.com/OpenDiablo2/OpenDiablo2/issues/794 // https://github.com/OpenDiablo2/OpenDiablo2/issues/794
callouts := []struct{ text string }{ callouts := []struct{ text string }{
// "Ctrl" should be hotkey // "Hold Down <%s> to Run" // "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" // "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" // "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" // "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 <Esc> to bring up the Game Menu" // "Hit <Esc> to bring up the Game Menu"
{text: d2tbl.TranslateString("StrHelp6")}, {text: d2tbl.TranslateString("StrHelp6")},
@ -355,7 +371,10 @@ func (h *Overlay) setupBulletedList() {
{text: d2tbl.TranslateString("StrHelp8")}, {text: d2tbl.TranslateString("StrHelp8")},
// "H" should be hotkey, // "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 { for idx := range callouts {
@ -372,7 +391,7 @@ func (h *Overlay) setupBulletedList() {
} }
// nolint:funlen // can't reduce // nolint:funlen // can't reduce
func (h *Overlay) setupLabelsWithLines() { func (h *HelpOverlay) setupLabelsWithLines() {
h.createCallout(callout{ h.createCallout(callout{
LabelText: d2tbl.TranslateString("strlvlup"), // "New Stats" LabelText: d2tbl.TranslateString("strlvlup"), // "New Stats"
LabelX: newStatsLabelX, LabelX: newStatsLabelX,
@ -533,7 +552,7 @@ type callout struct {
DotY int DotY int
} }
func (h *Overlay) createBullet(c callout) { func (h *HelpOverlay) createBullet(c callout) {
newLabel := h.uiManager.NewLabel(d2resource.FontFormal12, d2resource.PaletteSky) newLabel := h.uiManager.NewLabel(d2resource.FontFormal12, d2resource.PaletteSky)
newLabel.SetText(c.LabelText) newLabel.SetText(c.LabelText)
newLabel.SetPosition(c.LabelX, c.LabelY) newLabel.SetPosition(c.LabelX, c.LabelY)
@ -553,7 +572,7 @@ func (h *Overlay) createBullet(c callout) {
h.frames = append(h.frames, newDot) 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 := h.uiManager.NewLabel(d2resource.FontFormal12, d2resource.PaletteSky)
newLabel.SetText(c.LabelText) newLabel.SetText(c.LabelText)
newLabel.SetPosition(c.LabelX, c.LabelY) newLabel.SetPosition(c.LabelX, c.LabelY)
@ -561,7 +580,7 @@ func (h *Overlay) createLabel(c callout) {
newLabel.Alignment = d2gui.HorizontalAlignCenter 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 := h.uiManager.NewLabel(d2resource.FontFormal12, d2resource.PaletteSky)
newLabel.Color[0] = color.White newLabel.Color[0] = color.White
newLabel.SetText(c.LabelText) newLabel.SetText(c.LabelText)
@ -596,7 +615,7 @@ func (h *Overlay) createCallout(c callout) {
} }
// Render the overlay to the given surface // 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 { if !h.isOpen {
return nil return nil
} }

139
d2game/d2player/key_map.go Normal file
View File

@ -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
}