Skip to content

Commit

Permalink
Added ComplexBinds to CustomBinds page. Squashed commits:
Browse files Browse the repository at this point in the history
commit fba1fd0
Author: Russell Pickett <[email protected]>
Date:   Sun Nov 6 00:54:01 2022 -0400

    Correctly relayout page/CollapsiblePane when steps are added or deleted

commit b404da1
Author: Russell Pickett <[email protected]>
Date:   Thu Nov 3 02:46:27 2022 -0400

    Add ComplexBinds to Manual

commit 2f7f434
Author: Russell Pickett <[email protected]>
Date:   Thu Nov 3 02:29:50 2022 -0400

    ComplexBinds: Removing steps now renumbers the remaining ones

commit 5ce4700
Author: Russell Pickett <[email protected]>
Date:   Thu Nov 3 02:08:02 2022 -0400

    ComplexBinds:  Delete steps;  write binds.

commit b2f3436
Author: Russell Pickett <[email protected]>
Date:   Thu Nov 3 00:37:33 2022 -0400

    First work on ComplexBinds.  UI done, save/load done, PopulateBinds not
  • Loading branch information
emersonrp committed Nov 6, 2022
1 parent 2c471c1 commit 160d8f4
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 21 deletions.
12 changes: 10 additions & 2 deletions Help/Manual.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ <h3>PowerBinder</h3>
tool in BindControl, and can help create fun and useful behavior including activating powers, emotes, and costume changes;
targeting friends or foes by distance and name; Chat commands and feedback; and using Inspirations. It additionally allows
completely free-form commands to be added for the brave and experimental.</p>
<p>PowerBinder will allow you to add multiple powers to a single bind, but because of game constraints, <b>only one power
will be executed in a single bind</b>. For more info on what is and is not possible with custom binds, check the Homecoming
wiki for "The Incomplete and Unofficial Guide to /bind".</p>
<p>PowerBinder is a concept borrowed wholesale from citybinder, and owes much to it.</p>

<h3>Binds Directory</h3>
Expand Down Expand Up @@ -113,12 +116,17 @@ <h3>Gameplay</h3>
<h3>Custom Binds</h3>
<p>The Custom Binds page is where you'll create any bespoke binds you'd like that aren't covered by other pages and systems.</p>
<p><u>Simple Binds</u></p>
<p>To add a new Simple Bind to the page, click the New Simple Bind button at the bottom. You'll be prompted to name your
<p>To add a new Simple Bind to the page, click the New Simple Bind button at the top. You'll be prompted to name your
custom bind, and then presented with a small pane where you can select the key for the keybind and launch PowerBinder to
create the bind string. If the bind doesn't have both of these things complete, or ends up longer than 255 characters,
when you attempt to Write Binds, an error will be raised and the bind will not be written to the bindfiles.</p>
<p><u>Complex Binds</u></p>
<p>To add a new Complex Bind to the page, click the New Complex Bind button at the top. A complex bind allows you to
string together a sequence of steps, using PowerBinder, that will execute in rotation with repeated presses on the bind
key. This could be useful for creating sequences of attacks to be cast using (multiple presses of) a single key, to set
up emotes and chats and pet actions for roleplaying, or really for anything you can dream up.</p>
<p><u>Buffer Binds</u></p>
<p>To add a new Buffer Bind set to the page, click the New Buffer Bind button at the bottom. A buffer bind set comprises
<p>To add a new Buffer Bind set to the page, click the New Buffer Bind button at the top. A buffer bind set comprises
up to three buffing powers that will be cast sequentially by multiple presses of the same keybind, and keys for each team
member and/or pet that will do the buff rotation.</p>

Expand Down
12 changes: 10 additions & 2 deletions Page/CustomBinds.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@
import UI

from Page import Page
from UI.BufferBindPane import BufferBindPane
from UI.SimpleBindPane import SimpleBindPane
from UI.BufferBindPane import BufferBindPane
from UI.SimpleBindPane import SimpleBindPane
from UI.ComplexBindPane import ComplexBindPane

class CustomBinds(Page):
def __init__(self, parent):
Expand All @@ -22,6 +23,9 @@ def BuildPage(self):
newSimpleBindButton = wx.Button(self, -1, "New Simple Bind")
newSimpleBindButton.Bind(wx.EVT_BUTTON, self.OnNewSimpleBindButton)
buttonSizer.Add(newSimpleBindButton, wx.ALIGN_CENTER)
newComplexBindButton = wx.Button(self, -1, "New Complex Bind")
newComplexBindButton.Bind(wx.EVT_BUTTON, self.OnNewComplexBindButton)
buttonSizer.Add(newComplexBindButton, wx.ALIGN_CENTER)
newBufferBindButton = wx.Button(self, -1, "New Buffer Bind")
newBufferBindButton.Bind(wx.EVT_BUTTON, self.OnNewBufferBindButton)
buttonSizer.Add(newBufferBindButton, wx.ALIGN_CENTER)
Expand All @@ -48,6 +52,10 @@ def OnNewSimpleBindButton(self, evt):
self.AddBindToPage(bindpane = SimpleBindPane(self))
evt.Skip()

def OnNewComplexBindButton(self, evt):
self.AddBindToPage(bindpane = ComplexBindPane(self))
evt.Skip()

def OnNewBufferBindButton(self, evt):
self.AddBindToPage(bindpane = BufferBindPane(self))
evt.Skip()
Expand Down
29 changes: 14 additions & 15 deletions Profile.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import wx
import re
from pathlib import Path, PureWindowsPath
import json
import wx

from BindFile import BindFile

Expand All @@ -10,13 +10,13 @@
from Page.SoD import SoD
from Page.InspirationPopper import InspirationPopper
from Page.Mastermind import Mastermind
#from Page.ComplexBinds
from Page.CustomBinds import CustomBinds

import UI
from UI.ControlGroup import bcKeyButton
from UI.SimpleBindPane import SimpleBindPane
from UI.BufferBindPane import BufferBindPane
from UI.ComplexBindPane import ComplexBindPane

class Profile(wx.Notebook):

Expand All @@ -35,7 +35,6 @@ def __init__(self, parent, loadfile = None):
self.SoD = self.CreatePage(SoD(self))
self.InspirationPopper = self.CreatePage(InspirationPopper(self))
self.Mastermind = self.CreatePage(Mastermind(self))
#self.CreatePage(ComplexBinds(self))

# bind all control events so we can decide that we're modified.
for evt in [
Expand Down Expand Up @@ -66,17 +65,15 @@ def BindsDir(self) :
return Path(wx.ConfigBase.Get().Read('BindPath')) / self.Name()
def GameBindsDir(self) :
gbp = wx.ConfigBase.Get().Read('GameBindPath')
if gbp:
return PureWindowsPath(gbp) / self.Name()
else:
return self.BindsDir()
if gbp: return PureWindowsPath(gbp) / self.Name()
return self.BindsDir()

def BLF(self, *args):
filepath = self.GameBindsDir()
for arg in args: filepath = filepath / arg
return "$$bindloadfilesilent " + str(filepath)

def CheckConflict(self, key, button):
def CheckConflict(self, key):
conflicts = []

for pageName in self.Pages:
Expand Down Expand Up @@ -105,7 +102,7 @@ def SaveToFile(self, _ = None):

if fileDialog.ShowModal() == wx.ID_CANCEL:
wx.LogMessage("User canceled saving new profile")
return # the user changed their mind
return False # the user changed their mind

# Proceed loading the file chosen by the user
pathname = fileDialog.GetPath()
Expand All @@ -124,7 +121,7 @@ def doSaveToFile(self, _ = None):
if len(profilename) == 0 or re.search(" ", profilename):
wx.MessageBox("Profile Name is not valid, please correct this.")
self.ChangeSelection(0)
return
return False

self.ProfilePath().mkdir( parents = True, exist_ok = True )

Expand All @@ -141,7 +138,7 @@ def doSaveToFile(self, _ = None):
controlType = type(control).__name__
if controlType == 'DirPickerCtrl':
value = control.GetPath()
elif controlType == 'Button' or controlType == 'bcKeyButton':
elif controlType in ('Button', 'bcKeyButton'):
value = control.GetLabel()
elif controlType == 'ColourPickerCtrl':
value = control.GetColour().GetAsString(wx.C2S_HTML_SYNTAX)
Expand Down Expand Up @@ -202,13 +199,13 @@ def doLoadFromFile(self, pathname):
for controlname, control in page.Ctrls.items():
value = data[pagename].get(controlname, None)

if value == None: continue
if value is None: continue

# look up what type of control it is to know how to extract its value
controlType = type(control).__name__
if controlType == 'DirPickerCtrl':
control.SetPath(value)
elif controlType == 'Button' or controlType == 'bcKeyButton':
elif controlType in ('Button', 'bcKeyButton'):
control.SetLabel(value)
elif controlType == 'ColourPickerCtrl':
control.SetColour(value)
Expand Down Expand Up @@ -236,6 +233,9 @@ def doLoadFromFile(self, pathname):
elif custombind['Type'] == "BufferBind":
bindpane = BufferBindPane(cbpage, init = custombind)
cbpage.AddBindToPage(bindpane = bindpane)
elif custombind['Type'] == "ComplexBind":
bindpane = ComplexBindPane(cbpage, init = custombind)
cbpage.AddBindToPage(bindpane = bindpane)

self.Filename = Path(pathname)
wx.LogMessage(f"Loaded profile {pathname}")
Expand Down Expand Up @@ -342,10 +342,9 @@ def __init__(self, parent, msg = ''):
value = "/bindloadfile " + str(parent.GameBindsDir() / "reset.txt")
)
textCtrl.SetFont(
wx.Font(10, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, faceName = u'Courier')
wx.Font(10, wx.FONTFAMILY_TELETYPE, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, faceName = 'Courier')
)
sizer.Add( textCtrl, 0, wx.EXPAND|wx.LEFT|wx.RIGHT, 10)

sizer.Add( self.CreateButtonSizer(wx.OK), 0, wx.EXPAND|wx.ALL, 10)
self.SetSizerAndFit(sizer)

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Features
* Chat binds with optional 'typing' notifier
* Custom Binds
* create simple binds using PowerBinder, a flexible tool for putting together arbitrary commands into bind strings
* complex binds, chains of PowerBinder actions that fire sequentially on multiple presses of a keybind
* buffer binds, allowing quick one-key buffing of each teammate and/or pet
* Speed-on-Demand
* based on [citybinder](http://sourceforge.net/projects/citybinder/) and the original Gnarly's SoD keybinds
Expand All @@ -47,7 +48,6 @@ TODO
* Support [Homecoming travel power changes](https://forums.homecomingservers.com/topic/27807-travel-power-updates-in-issue-27-page-2/) in SoD
* Kheldian form/travel binds in speed-on-demand
* Temporary powers in speed-on-demand
* Additional custom bind types
* Roll standalone binaries for Windows, MacOS, Linux
* In progress... TODO: sign / notarize MacOS App?
* More and better help text and documentation
Expand Down
123 changes: 123 additions & 0 deletions UI/ComplexBindPane.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import wx
import UI
from UI.CustomBindPaneParent import CustomBindPaneParent
from UI.KeySelectDialog import bcKeyButton, EVT_KEY_CHANGED
from UI.PowerBinderDialog import PowerBinderButton

class ComplexBindPane(CustomBindPaneParent):
def __init__(self, page, init = {}):
CustomBindPaneParent.__init__(self, page, init)

self.Steps = []

def Serialize(self):
data = {
'Type' : 'ComplexBind',
'Title': self.Title,
'Key' : self.Ctrls['BindKey'].GetLabel(),
'Steps': [],
}
for step in self.Steps:
if step.BindContents.GetValue():
data['Steps'].append({
'contents' : step.BindContents.GetValue(),
'powerbinderdata' : step.PowerBinder.SaveToData()
})
return data

def BuildBindUI(self, page):
pane = self.GetPane()

self.BindSizer = wx.BoxSizer(wx.HORIZONTAL)
self.BindStepSizer = wx.BoxSizer(wx.VERTICAL)
AddBindStepButton = wx.Button(pane, -1, "Add Step...")
AddBindStepButton.Bind(wx.EVT_BUTTON, self.onAddStepButton)
self.BindStepSizer.Add(AddBindStepButton, 0, wx.TOP, 10)
if self.Init.get('Steps', ''):
for step in self.Init['Steps']:
self.onAddStepButton(None, step)
else:
self.onAddStepButton()

self.BindSizer.Add ( self.BindStepSizer, 1, wx.EXPAND)

BindKeyCtrl = bcKeyButton(pane, -1, {
'CtlName' : f"{self.bindclass}BindKey",
'Page' : page,
'Key' : self.Init.get('Key', ''),
})
#BindKeyCtrl.Bind(EVT_KEY_CHANGED, self.onKeyChanged)
BindKeySizer = wx.BoxSizer(wx.HORIZONTAL)
BindKeySizer.Add(wx.StaticText(pane, -1, "Bind Key:"), 0, wx.ALIGN_CENTER_VERTICAL|wx.LEFT|wx.RIGHT, 5)
BindKeySizer.Add(BindKeyCtrl, 0)
self.BindSizer.Add(BindKeySizer, 0, wx.LEFT|wx.RIGHT, 10)
self.Ctrls['BindKey'] = BindKeyCtrl
UI.Labels[BindKeyCtrl.CtlName] = "Complex Bind "

self.BindSizer.Layout()

# border around the addr box
border = wx.BoxSizer(wx.VERTICAL)
border.Add(self.BindSizer, 1, wx.EXPAND|wx.ALL, 10)
pane.SetSizer(border)

def onAddStepButton(self, _ = None, stepdata = {}):
step = self.MakeBindStepUI(self.GetPane(), stepdata)
self.BindStepSizer.Insert(self.BindStepSizer.GetItemCount()-1, step, 0, wx.EXPAND)
self.Steps.append(step)
self.Page.Layout()
self.Profile.SetModified()

def MakeBindStepUI(self, parent, step):
stepNumber = self.BindStepSizer.GetItemCount() # is already the next step because of the add button
panel = wx.Panel(parent)
sizer = wx.BoxSizer(wx.HORIZONTAL)

panel.StepLabel = wx.StaticText(panel, -1, f"Step {stepNumber}:")
sizer.Add(panel.StepLabel, 0, wx.ALIGN_CENTER_VERTICAL)

panel.BindContents= wx.TextCtrl(panel, -1, step.get('contents', ''))
sizer.Add(panel.BindContents, 1, wx.EXPAND|wx.LEFT|wx.RIGHT, 5)

panel.PowerBinder = PowerBinderButton(panel, panel.BindContents, step.get('powerbinderdata', {}))
sizer.Add(panel.PowerBinder, 0)

delButton = wx.Button(panel, -1, "X", size = (40,-1))
delButton.SetForegroundColour(wx.RED)
delButton.Bind(wx.EVT_BUTTON, self.onDelButton)
sizer.Add(delButton, 0)

panel.SetSizer(sizer)

return panel

def onDelButton(self, evt):
button = evt.EventObject
step = button.GetParent()
self.Steps.remove(step)
step.Destroy()
self.RenumberSteps()
self.Page.Layout()
self.Profile.SetModified()

def onContentsChanged(self, _ = None):
pass

def RenumberSteps(self):
for i, step in enumerate(self.Steps, start = 1):
step.StepLabel.SetLabel(f"Step {i}:")
#self.Layout()

def PopulateBindFiles(self):
resetfile = self.Profile.ResetFile()
# fish out only the steps that have contents
fullsteps = list(filter(lambda x: x.BindContents.GetValue(), self.Steps))
for i, step in enumerate(fullsteps, start = 1):
cbindfile = self.Profile.GetBindFile("cbinds", f"{self.Title}-{i}.txt")
nextCycle = 1 if (i+1 > len(fullsteps)) else i+1

cmd = [step.BindContents.GetValue(), self.Profile.BLF(f'cbinds\\{self.Title}-{nextCycle}.txt')]
key = self.Ctrls['BindKey'].GetLabel()

if i == 1: resetfile.SetBind(key, self, self.Title, cmd)
cbindfile.SetBind(key, self, self.Title, cmd)
2 changes: 1 addition & 1 deletion UI/KeySelectDialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ def handleBind(self, event):

Profile = wx.App.Get().Profile
if Profile:
conflicts = Profile.CheckConflict(self.Binding, self.Button)
conflicts = Profile.CheckConflict(self.Binding)
if conflicts:
conflictString = ''
for conflict in conflicts:
Expand Down

0 comments on commit 160d8f4

Please sign in to comment.