Page 1 of 2

Persistent build spacing

Posted: 07 Sep 2011, 01:02
by DrHash
Hi everyone,

I had some free time and decided to program a Lua widget that keeps track of the last chosen build spacing for each building. Next time we request to build the same building, the last chosen build spacing for that building will be automatically restored for us. As well, build spacing settings are stored in a separate file for each game once the game is over, so we can keep build spacing settings for every game. Finally, the widget allows to use the mouse well + shift for changing build spacing... nice logitech mouses have wheels that can turn pretty fast :P My 4th and 5th mouse buttons are not recognized in my Spring under Ubuntu, so this does the trick.

Well, with no more delay, here you are the source code:

Code: Select all

--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--
--  file:    gui_persistent_build_spacing.lua
--  brief:   Recalls last build spacing set for each building and game, and allows to modify build spacing with mouse wheel + shift
--  author:  DrHash
--  version: 1.00
--
--  Copyright (C) 2011.
--  Licensed under the terms of the GNU GPL, v3 or later.
--
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------

function widget:GetInfo()
  return {
    name      = "Persistent Build Spacing",
    desc      = "Recalls last build spacing set for each building and game, and allows to modify build spacing with mouse wheel + shift",
    author    = "DrHash",
    date      = "Sep 6, 2011",
    license   = "GNU GPL, v3 or later",
    layer     = 0,
    enabled   = true  --  loaded by default?
  }
end

local buildSpacingTable = {}
local defaultBuildSpacing = 0
local currentBuildSpacing = 0
local lastActiveCommandId = nil
local currentBuildingCode = nil
local GetActiveCommand = Spring.GetActiveCommand
local Echo = Spring.Echo
local SendCommands = Spring.SendCommands
local pathToSave = "LuaUI/Widgets/BuildSpacing/" -- where to store build spacing presets
local filename

function LoadBuildSpacingTable(file)
  local buildCode, buildSpacing, i, j
  local line = file:read()
  while (line) do
    i, j = string.find(line, ", ")
    if (not i) then break end
    buildCode = string.sub(line, 1, i - 1)
    buildSpacing = tonumber(string.sub(line, j + 1))
    buildSpacingTable[buildCode] = buildSpacing
    line = file:read()
  end
end

local function SaveBuildSpacingTable(file)
  local buildCode, buildSpacing
  for buildCode, buildSpacing in pairs(buildSpacingTable) do
    file:write(buildCode .. ", " .. buildSpacing .. "\n")
  end
end

-- Load last saved build spacing table for current game, if present
function widget:Initialize()
--  if Spring.GetSpectatingState() or Spring.IsReplay() then
--    Echo("Persistent Build Spacing widget widget disabled for spectators")
--    widgetHandler:RemoveWidget()
--  end
  Spring.CreateDir(pathToSave)

  local modShortName = Game.modShortName
  filename = (pathToSave .. modShortName .. ".txt")
  local file = io.open(filename,'r')
  if file then
    Echo("Build spacing table detected for " .. modShortName .. " - loading...")
    LoadBuildSpacingTable(file)
    file:close()
  end
end

-- Upon game over, save the build spacing table; however, if you exit the game with ESC + QUIT, build spacing will not be stored!!
-- Is there some alternative callin function??
function widget:GameOver()
  local file = io.open(filename,'w')
  if file then
    Echo("Saving build spacing table to " .. filename);
    SaveBuildSpacingTable(file)
    file:close()
  end
end

-- Keep track of a build spacing increment
function noticeBuildSpacingInc()
  currentBuildSpacing = currentBuildSpacing + 1
  if (currentBuildingCode) then
    buildSpacingTable[currentBuildingCode] = currentBuildSpacing
  end
end

-- Keep track of a build spacing decrement
function noticeBuildSpacingDec()
  if (currentBuildSpacing > 0) then
    currentBuildSpacing = currentBuildSpacing - 1
    if (currentBuildingCode) then
      buildSpacingTable[currentBuildingCode] = currentBuildSpacing
    end
  end
end

-- Allow mouse wheel + shift to change build spacing
function widget:MouseWheel(up, value)
  local alt,ctrl,meta,shift = Spring.GetModKeyState()
  if (shift) then
    if (up) then
      Spring.SendCommands("buildspacing dec")
      noticeBuildSpacingDec()
    else
      Spring.SendCommands("buildspacing inc")
      noticeBuildSpacingInc()
    end
    return true
  end
  return false
end

-- Sets build spacing as stored for the current selected building, or to the default spacing in case none defined
function setBuildSpacing()
  local storedBuildSpacing = buildSpacingTable[currentBuildingCode]
  if (not storedBuildSpacing) then
    buildSpacingTable[currentBuildingCode] = defaultBuildSpacing
    storedBuildSpacing = defaultBuildSpacing
  end

  while (storedBuildSpacing > currentBuildSpacing) do
    Spring.SendCommands("buildspacing inc")
    currentBuildSpacing = currentBuildSpacing + 1
  end
  while (storedBuildSpacing < currentBuildSpacing) do
    Spring.SendCommands("buildspacing dec")
    currentBuildSpacing = currentBuildSpacing - 1
  end
end

-- Resets build spacing to the predefined value for the last building that has been chosen to build
-- but allows to manually modify build spacing afterwards, and the new build spacing will be recalled later
function widget:DrawScreen()
  local _, currentActiveCommandId, _, currentActiveCommandName = GetActiveCommand()
  -- currentActiveCommandId >= 0 for commands other than those that build buildings
  if (not currentActiveCommandId) or (currentActiveCommandId >= 0) or (currentActiveCommandId == lastActiveCommandId) then
    return
  end
  lastActiveCommandId = currentActiveCommandId
  currentBuildingCode = currentActiveCommandName
  setBuildSpacing()
end


-- This keeps build spacing counter up to date upon pressing mouse buttons 4 or 5
-- Note build spacing is modified by those buttons when widget gui_buildspacing.lua is active (it is by default as for BA version 7.50)
-- If you use these buttons for other purposes, remove or comment this function

--[[
function widget:MousePress(mx, my, button)
  if (button == 4) then
    noticeBuildSpacingInc()
  elseif (button == 5) then
    noticeBuildSpacingDec()
  end
  return false
end
--]]

--Uncomment the remaining code if you also use keys for changing build spacing
--If you use other keys than Z or X, simply replace KEYSYMS.Z and KEYSYMS.X by the corresponding key codes

--[[
include("keysym.h.lua")

function widget:KeyPress(key, modifier, isRepeat)
  if (key == KEYSYMS.Z) then
    noticeBuildSpacingInc()
  elseif (key == KEYSYM.X) then
    noticeBuildSpacingDec()
  end
end
--]]
This is my first experience with Lua, so I'm open to suggestions. It is not my first programming experience, though... I've already programmed in Java, C++, C, Perl, PASCAL, BASIC, Logo, LISP, Prolog, and other languages that I don't even recall.

Things that I don't like about this widget:

- it assumes build spacing is set to 0 upon game start, then simply catches key and mouse events that trigger a change in build spacing in order to keep a build spacing counter up to date. Failing to catch some of those keystrokes will result in a strange behavior. It would be nicer to be able to catch buildspacing inc/dec commands, or even nicer to be able to read/write the buildspacing variable inside the spring engine!! However, I did not find a way of doing so, so each one must modify the script for ensuring that every key/mouse event modifying build spacing is caught and properly treated, depending on your own configurations (uikeys.txt and widgets).

- finally, the build spacing settings are stored upon game over; however, if the player exits by pressing ESC + QUIT then the settings are not recorded. A callin function catching the game exiting should be used instead, but I neither found such feature.

So, enjoy this widget and Spring forever!!

DrHash

Re: Persistent build spacing

Posted: 07 Sep 2011, 01:28
by DrHash
I just solved the GameOver problem, thanks to [LCC]jK who suggested to use function GetConfigData instead. Here you are the new version:

Code: Select all

--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--
--  file:    gui_persistent_build_spacing.lua
--  brief:   Recalls last build spacing set for each building and game, and allows to modify build spacing with mouse wheel + shift
--  author:  DrHash
--  version: 1.00
--
--  Copyright (C) 2011.
--  Licensed under the terms of the GNU GPL, v3 or later.
--
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------

function widget:GetInfo()
  return {
    name      = "Persistent Build Spacing",
    desc      = "Recalls last build spacing set for each building and game, and allows to modify build spacing with mouse wheel + shift",
    author    = "DrHash",
    date      = "Sep 6, 2011",
    license   = "GNU GPL, v3 or later",
    layer     = 0,
    enabled   = true  --  loaded by default?
  }
end

local buildSpacingTable = {}
local defaultBuildSpacing = 0
local currentBuildSpacing = 0
local lastActiveCommandId = nil
local currentBuildingCode = nil
local GetActiveCommand = Spring.GetActiveCommand
local Echo = Spring.Echo
local SendCommands = Spring.SendCommands
local pathToSave = "LuaUI/Widgets/BuildSpacing/" -- where to store build spacing presets
local filename

function LoadBuildSpacingTable(file)
  local buildCode, buildSpacing, i, j
  local line = file:read()
  while (line) do
    i, j = string.find(line, ", ")
    if (not i) then break end
    buildCode = string.sub(line, 1, i - 1)
    buildSpacing = tonumber(string.sub(line, j + 1))
    buildSpacingTable[buildCode] = buildSpacing
    line = file:read()
  end
end

local function SaveBuildSpacingTable(file)
  local buildCode, buildSpacing
  for buildCode, buildSpacing in pairs(buildSpacingTable) do
    file:write(buildCode .. ", " .. buildSpacing .. "\n")
  end
end

-- Load last saved build spacing table for current game, if present
function widget:Initialize()
--  if Spring.GetSpectatingState() or Spring.IsReplay() then
--    Echo("Persistent Build Spacing widget widget disabled for spectators")
--    widgetHandler:RemoveWidget()
--  end
  Spring.CreateDir(pathToSave)

  local modShortName = Game.modShortName
  filename = (pathToSave .. modShortName .. ".txt")
  local file = io.open(filename,'r')
  if file then
    Echo("Build spacing table detected for " .. modShortName .. " - loading...")
    LoadBuildSpacingTable(file)
    file:close()
  end
end

-- Right before unloading the widget, save the build spacing table
function widget:GetConfigData()
  local file = io.open(filename,'w')
  if file then
    Echo("Saving build spacing table to " .. filename);
    SaveBuildSpacingTable(file)
    file:close()
  end
end

-- Keep track of a build spacing increment
function noticeBuildSpacingInc()
  currentBuildSpacing = currentBuildSpacing + 1
  if (currentBuildingCode) then
    buildSpacingTable[currentBuildingCode] = currentBuildSpacing
  end
end

-- Keep track of a build spacing decrement
function noticeBuildSpacingDec()
  if (currentBuildSpacing > 0) then
    currentBuildSpacing = currentBuildSpacing - 1
    if (currentBuildingCode) then
      buildSpacingTable[currentBuildingCode] = currentBuildSpacing
    end
  end
end

-- Allow mouse wheel + shift to change build spacing
function widget:MouseWheel(up, value)
  local alt,ctrl,meta,shift = Spring.GetModKeyState()
  if (shift) then
    if (up) then
      Spring.SendCommands("buildspacing dec")
      noticeBuildSpacingDec()
    else
      Spring.SendCommands("buildspacing inc")
      noticeBuildSpacingInc()
    end
    return true
  end
  return false
end

-- Sets build spacing as stored for the current selected building, or to the default spacing in case none defined
function setBuildSpacing()
  local storedBuildSpacing = buildSpacingTable[currentBuildingCode]
  if (not storedBuildSpacing) then
    buildSpacingTable[currentBuildingCode] = defaultBuildSpacing
    storedBuildSpacing = defaultBuildSpacing
  end

  while (storedBuildSpacing > currentBuildSpacing) do
    Spring.SendCommands("buildspacing inc")
    currentBuildSpacing = currentBuildSpacing + 1
  end
  while (storedBuildSpacing < currentBuildSpacing) do
    Spring.SendCommands("buildspacing dec")
    currentBuildSpacing = currentBuildSpacing - 1
  end
end

-- Resets build spacing to the predefined value for the last building that has been chosen to build
-- but allows to manually modify build spacing afterwards, and the new build spacing will be recalled later
function widget:DrawScreen()
  local _, currentActiveCommandId, _, currentActiveCommandName = GetActiveCommand()
  -- currentActiveCommandId >= 0 for commands other than those that build buildings
  if (not currentActiveCommandId) or (currentActiveCommandId >= 0) or (currentActiveCommandId == lastActiveCommandId) then
    return
  end
  lastActiveCommandId = currentActiveCommandId
  currentBuildingCode = currentActiveCommandName
  setBuildSpacing()
end


-- This keeps build spacing counter up to date upon pressing mouse buttons 4 or 5
-- Note build spacing is modified by those buttons when widget gui_buildspacing.lua is active (it is by default as for BA version 7.50)
-- If you use these buttons for other purposes, remove or comment this function

--[[
function widget:MousePress(mx, my, button)
  if (button == 4) then
    noticeBuildSpacingInc()
  elseif (button == 5) then
    noticeBuildSpacingDec()
  end
  return false
end
--]]

--Uncomment the remaining code if you also use keys for changing build spacing
--If you use other keys than Z or X, simply replace KEYSYMS.Z and KEYSYMS.X by the corresponding key codes

--[[
include("keysym.h.lua")

function widget:KeyPress(key, modifier, isRepeat)
  if (key == KEYSYMS.Z) then
    noticeBuildSpacingInc()
  elseif (key == KEYSYM.X) then
    noticeBuildSpacingDec()
  end
end
--]]

Re: Persistent build spacing

Posted: 07 Sep 2011, 12:14
by Beherith
Wow, I like this widget very much, super useful for mines and wind and nanos.

I'll check it out once i get home to a proper pc.

Re: Persistent build spacing

Posted: 07 Sep 2011, 12:34
by DrHash
Thanks! Hope it will be useful ;)

Re: Persistent build spacing

Posted: 07 Sep 2011, 12:44
by knorke
welcome to the forum.
good idea, i think that will turn out usefull. :)
Failing to catch some of those keystrokes will result in a strange behavior. It would be nicer to be able to catch buildspacing inc/dec commands, or even nicer to be able to read/write the buildspacing variable inside the spring engine!!
Would be nice indeed.
Atm that prevents the widget from being bundled with a game because players need to adjust it for their config/keyboard layout (us/eu)
zero-K's camera widget had the problem with "stuck keys" too I think.

also maybe give an error message when file writing/saving failed?

Re: Persistent build spacing

Posted: 07 Sep 2011, 15:38
by DrHash
I added the warning message upon saving fail... catching build spacing commands is still a mystery to me, dunno even if it is possible. Any ideas will be welcome. At least I added functions at the end of the code for catching mouse buttons and keystrokes modifying build spacing. Uncomment/comment/modify them in order to suit your configuration. Here you are the code:

Code: Select all

--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--
--  file:    gui_persistent_build_spacing.lua
--  brief:   Recalls last build spacing set for each building and game, and allows to modify build spacing with mouse wheel + shift
--  author:  DrHash
--  version: 1.00
--
--  Copyright (C) 2011.
--  Licensed under the terms of the GNU GPL, v3 or later.
--
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------

function widget:GetInfo()
  return {
    name      = "Persistent Build Spacing",
    desc      = "Recalls last build spacing set for each building and game, and allows to modify build spacing with mouse wheel + shift",
    author    = "DrHash",
    date      = "Sep 6, 2011",
    license   = "GNU GPL, v3 or later",
    layer     = 0,
    enabled   = true  --  loaded by default?
  }
end

local buildSpacingTable = {}
local defaultBuildSpacing = 0
local currentBuildSpacing = 0
local lastActiveCommandId = nil
local currentBuildingCode = nil
local GetActiveCommand = Spring.GetActiveCommand
local Echo = Spring.Echo
local SendCommands = Spring.SendCommands
local pathToSave = "LuaUI/Widgets/BuildSpacing/" -- where to store build spacing presets
local filename

function LoadBuildSpacingTable(file)
  local buildCode, buildSpacing, i, j
  local line = file:read()
  while (line) do
    i, j = string.find(line, ", ")
    if (not i) then break end
    buildCode = string.sub(line, 1, i - 1)
    buildSpacing = tonumber(string.sub(line, j + 1))
    buildSpacingTable[buildCode] = buildSpacing
    line = file:read()
  end
end

local function SaveBuildSpacingTable(file)
  local buildCode, buildSpacing
  for buildCode, buildSpacing in pairs(buildSpacingTable) do
    file:write(buildCode .. ", " .. buildSpacing .. "\n")
  end
end

-- Load last saved build spacing table for current game, if present
function widget:Initialize()
--  if Spring.GetSpectatingState() or Spring.IsReplay() then
--    Echo("Persistent Build Spacing widget widget disabled for spectators")
--    widgetHandler:RemoveWidget()
--  end
  Spring.CreateDir(pathToSave)

  local modShortName = Game.modShortName
  filename = (pathToSave .. modShortName .. ".txt")
  local file = io.open(filename,'r')
  if file then
    Echo("Build spacing table detected for " .. modShortName .. " - loading...")
    LoadBuildSpacingTable(file)
    file:close()
  end
end

-- Upon game over, save the build spacing table; however, if you exit the game with ESC + QUIT, build spacing will not be stored!!
-- Is there some alternative callin function??
function widget:GetConfigData()
  local file = io.open(filename,'w')
  if file then
    Echo("Saving build spacing table to " .. filename)
    SaveBuildSpacingTable(file)
    file:close()
  else
    Echo("Could not open file" .. filename .. " for saving build spacing table")
  end
end

-- Keep track of a build spacing increment
function noticeBuildSpacingInc()
  currentBuildSpacing = currentBuildSpacing + 1
  if (currentBuildingCode) then
    buildSpacingTable[currentBuildingCode] = currentBuildSpacing
  end
end

-- Keep track of a build spacing decrement
function noticeBuildSpacingDec()
  if (currentBuildSpacing > 0) then
    currentBuildSpacing = currentBuildSpacing - 1
    if (currentBuildingCode) then
      buildSpacingTable[currentBuildingCode] = currentBuildSpacing
    end
  end
end

-- Allow mouse wheel + shift to change build spacing
function widget:MouseWheel(up, value)
  local alt,ctrl,meta,shift = Spring.GetModKeyState()
  if (shift) then
    if (up) then
      Spring.SendCommands("buildspacing dec")
      noticeBuildSpacingDec()
    else
      Spring.SendCommands("buildspacing inc")
      noticeBuildSpacingInc()
    end
    return true
  end
  return false
end

-- Sets build spacing as stored for the current selected building, or to the default spacing in case none defined
function setBuildSpacing()
  local storedBuildSpacing = buildSpacingTable[currentBuildingCode]
  if (not storedBuildSpacing) then
    buildSpacingTable[currentBuildingCode] = defaultBuildSpacing
    storedBuildSpacing = defaultBuildSpacing
  end

  while (storedBuildSpacing > currentBuildSpacing) do
    Spring.SendCommands("buildspacing inc")
    currentBuildSpacing = currentBuildSpacing + 1
  end
  while (storedBuildSpacing < currentBuildSpacing) do
    Spring.SendCommands("buildspacing dec")
    currentBuildSpacing = currentBuildSpacing - 1
  end
end

-- Resets build spacing to the predefined value for the last building that has been chosen to build
-- but allows to manually modify build spacing afterwards, and the new build spacing will be recalled later
function widget:DrawScreen()
  local _, currentActiveCommandId, _, currentActiveCommandName = GetActiveCommand()
  -- currentActiveCommandId >= 0 for commands other than those that build buildings
  if (not currentActiveCommandId) or (currentActiveCommandId >= 0) or (currentActiveCommandId == lastActiveCommandId) then
    return
  end
  lastActiveCommandId = currentActiveCommandId
  currentBuildingCode = currentActiveCommandName
  setBuildSpacing()
end


-- This keeps build spacing counter up to date upon pressing mouse buttons 4 or 5
-- Note build spacing is modified by those buttons when widget gui_buildspacing.lua is active (it is by default as for BA version 7.50)
-- If you use these buttons for other purposes, remove or comment this function

--[[
function widget:MousePress(mx, my, button)
  if (button == 4) then
    noticeBuildSpacingInc()
  elseif (button == 5) then
    noticeBuildSpacingDec()
  end
  return false
end
--]]

--Uncomment the remaining code if you also use keys for changing build spacing
--If you use other keys than Z or X, simply replace KEYSYMS.Z and KEYSYMS.X by the corresponding key codes

--[[
include("keysym.h.lua")

function widget:KeyPress(key, modifier, isRepeat)
  if (key == KEYSYMS.Z) then
    noticeBuildSpacingInc()
  elseif (key == KEYSYM.X) then
    noticeBuildSpacingDec()
  end
end
--]]

Re: Persistent build spacing

Posted: 07 Sep 2011, 18:51
by very_bad_soldier
Yeah nice1, good idea, I like it!

Just a few suggestions:
-it seems you are writing the config file on your own directly into the widget folder. Better save it into the folder LuaUI/Config.
Ideally use the GetConfigData/SetConfigData callins to write and read widget config info (they are game specific also).

EDIT:
Ok sorry, you are using GetConfigData already. But you dont need to handle file IO yourself there. Just return a table with your configuration and it will be saved for you:

Code: Select all

function widget:GetConfigData()
	printDebug("Saving config. Bottom: " .. buttonConfig["posPercBottom"] )
	local data = {}
	data["buttons"] = buttonConfig["enabled"]
	data["positionPercentageRight"] = buttonConfig["posPercRight"]
	data["positionPercentageBottom"] = buttonConfig["posPercBottom"]
  
	return data
end
Same for SetConfigData, jsut the other way round (you will get your table back you saved before):

Code: Select all

function widget:SetConfigData(data) 
	printDebug("Loading config...")
	if (data ~= nil) then
		if ( data["buttons"] ~= nil ) then
			buttonConfig["enabled"] = data["buttons"]
			printDebug("enabled config found...")
		end
		
		if ( data["positionPercentageRight"] ~= nil ) then
			buttonConfig["posPercRight"] = data["positionPercentageRight"]
			printDebug("right pos config found...")
		end
		
		if ( data["positionPercentageBottom"] ~= nil ) then
			buttonConfig["posPercBottom"] = data["positionPercentageBottom"]
			printDebug("bottom config found: " .. buttonConfig["posPercBottom"])
		end
	end
end
-personally I dont like the included mousewheel function. Shift+mousewheel is already the function for fast zooming (at least for me, I dont know if its default engine behavior but I think so) which gets overwritten by your build spacing functions. Besides that I have problems with my fingers using the mousewheel while pressing left mouse button cause I usually use my forefinger for the mousewheel but that might be just me. So I will better stick to the keyboard shortcuts. But thanks to your widget I will probably have to use them very rarely in the future ;)

Re: Persistent build spacing

Posted: 07 Sep 2011, 19:04
by very_bad_soldier

Re: Persistent build spacing

Posted: 07 Sep 2011, 20:16
by DrHash
Ok, thanks for the suggestions. I'll later check how to use the built-in methods for storing config files. I was unsure of whether configuration data for different mods would mixed together or not.

About the fast mouse wheel mode... I've got a logitech G9 mouse; its mouse wheel has 2 operation modes, step by step or free rotation. In the former mode you feel the clicks after each rotating step, but in the latter mode you can just hit the wheel with your finger and let it rotate. It goes pretty fast, so I did not even think about a keystroke for fast zooming. I think other logitech mouses such as MX1000 have the same functionality. Anyways, instead of using shift you could use another key, or simply comment out the function modifying the mouse wheel behavior. I wanted to write two separated widgets, since they offer 2 different functionalities, but finally wrote both of them in a single widget since the build spacing persistence requires to monitor events that alter build spacing... there would be no problem if different widgets could share variables; is it possible? how? Even better, if I could catch build spacing commands, both widgets could operate independently of each other.

In the end, the "hidden" idea of this widget was to free build spacing keys Z and X in order to use them as hotkeys for building ground and air defense buildings.

Re: Persistent build spacing

Posted: 07 Sep 2011, 20:43
by very_bad_soldier

Re: Persistent build spacing

Posted: 07 Sep 2011, 23:34
by albator
Sooooo useful (no irony, true story)

Re: Persistent build spacing

Posted: 08 Sep 2011, 17:57
by DrHash
After studying the suggestions made by very_bad_soldier, I have come out with a quite neater version of the widget. The new features are:

- the use of the built-in system for storing configuration data (if you have already used a previous version of this widget, recall to delete folder "LuaUI/Widgets/BuildSpacing")

- direct access to Spring build spacing variables rather than monitoring key/mouse events modifying build spacing, so finally this version can be used as is by any player, that is, without having to adapt the code to our custom configurations

- the widget is now split into two widgets, one for recalling build spacing settings and other for using the mouse wheel for changing build spacing

- build spacing can be changed with the mouse wheel only when we are building something; in other cases the mouse wheel will behave as usual, hence we can still use shift + mouse wheel for fast zooming when not building anything

So, here you are the source code of gui_persistent_build_spacing.lua:

Code: Select all

--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--
--  file:    gui_persistent_build_spacing.lua
--  brief:   Recalls last build spacing set for each building and game
--  author:  DrHash
--  version: 2.0
--
--  Copyright (C) 2011.
--  Licensed under the terms of the GNU GPL, v3 or later.
--
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------

function widget:GetInfo()
  return {
    name      = "Persistent Build Spacing",
    desc      = "Recalls last build spacing set for each building and game",
    author    = "DrHash",
    date      = "Sep 8, 2011",
    license   = "GNU GPL, v3 or later",
    layer     = 0,
    enabled   = true  --  loaded by default?
  }
end

local buildSpacingTable = {}
local defaultBuildSpacing = 0
local lastStoredBuildSpacing = 0
local lastActiveCommandId = nil
local currentBuildingCode = nil
local GetActiveCommand = Spring.GetActiveCommand
local GetBuildSpacing = Spring.GetBuildSpacing
local SetBuildSpacing = Spring.SetBuildSpacing

--- Upon loading the widget, restore the build spacing table if there was one stored in the current's mod config file
function widget:SetConfigData(data)
  if (data) then
    buildSpacingTable = data
  end
  for i, j in pairs(buildSpacingTable) do
    Spring.Echo(i, j)
  end
end

-- Upon unloading the widget, save the build spacing table in the current's mod config file
function widget:GetConfigData()
  return buildSpacingTable
end

-- Resets build spacing to the predefined value for the last building that has been chosen to build,
-- but allows to manually modify build spacing afterwards, and the new build spacing will be recalled later
function widget:DrawScreen()
  local _, currentActiveCommandId, _, currentActiveCommandName = GetActiveCommand()
  -- currentActiveCommandId >= 0 for commands other than those that build buildings
  if (not currentActiveCommandId) or (currentActiveCommandId >= 0) then
    return --Nothing to do if we are not building something
  end
  -- If we are still building the same kind of build, keep track of changes in build spacing for that building
  if (currentActiveCommandId == lastActiveCommandId) then
    if (GetBuildSpacing() ~= lastStoredBuildSpacing) then
      buildSpacingTable[currentBuildingCode] = GetBuildSpacing()
      lastStoredBuildSpacing = GetBuildSpacing()
    end
  -- If we just chose a new kind of building to build, restore the last tracked build spacing for that building, or set it to the default value if none
  else
    lastActiveCommandId = currentActiveCommandId
    currentBuildingCode = currentActiveCommandName
    lastStoredBuildSpacing = buildSpacingTable[currentBuildingCode]
    if (not lastStoredBuildSpacing) then
      buildSpacingTable[currentBuildingCode] = defaultBuildSpacing
      lastStoredBuildSpacing = defaultBuildSpacing
    end
    SetBuildSpacing(lastStoredBuildSpacing)
  end
end
and here you are the code of gui_mouse_wheel_spacing.lua:

Code: Select all

--------------------------------------------------------------------------------
--------------------------------------------------------------------------------
--
--  file:    gui_mouse_wheel_spacing.lua
--  brief:   Use mouse wheel for buildspacing when building lines or matrices of buildings
--  author:  DrHash
--  version: 1.00
--
--  Copyright (C) 2011.
--  Licensed under the terms of the GNU GPL, v3 or later.
--
--------------------------------------------------------------------------------
--------------------------------------------------------------------------------

function widget:GetInfo()
  return {
    name      = "Mouse Wheel Spacing",
    desc      = "Use mouse wheel for changing build spacing when building lines or matrices of buildings",
    author    = "DrHash",
    date      = "Sep 8, 2011",
    license   = "GNU GPL, v3 or later",
    layer     = 0,
    enabled   = true  --  loaded by default?
  }
end

local GetModKeyState = Spring.GetModKeyState
local GetActiveCommand = Spring.GetActiveCommand
local SendCommands = Spring.SendCommands

function widget:MouseWheel(up, value)
  local alt,ctrl,meta,shift = GetModKeyState()
  local _, currentActiveCommandId, _, _ = GetActiveCommand()
  -- If we are building a building and we are pressing Shift, change build spacing
  if (currentActiveCommandId and currentActiveCommandId < 0 and shift) then
    if (up) then
      SendCommands("buildspacing dec")
    else
      SendCommands("buildspacing inc")
    end
    return true
  end
  -- Otherwise let mouse wheel be normally handled
  return false
end
There is already a gui_buildspacing.lua widget bundled with Spring which allows to use mouse buttons 4 & 5 for changing build spacing. Perhaps the code of gui_mouse_wheel_spacing.lua could be integrated into gui_buildspacing.lua, or remain as a separate widget so that we can selectively activate/deactivate them. If they are to remain separated, I would suggest to change the name of widget gui_buildspacing.lua to gui_mouse_button_spacing.lua in order to clearly distinguish both widgets.

Cheers,

DrHash

Re: Persistent build spacing

Posted: 09 Sep 2011, 05:49
by Google_Frog
Why function widget:DrawScreen()? It updates more often than function widget:Update() and it doesn't look like this widget needs to update that often.

Re: Persistent build spacing

Posted: 09 Sep 2011, 08:14
by FLOZi
I was told fairly recently that Update() is called for every draw frame (a way to keep draw callins to only OGL calls)

Re: Persistent build spacing

Posted: 09 Sep 2011, 08:16
by very_bad_soldier
Maybe widget:GameFrame() is the right callin. Gets only called for every simulation frame IIRC.

Re: Persistent build spacing

Posted: 09 Sep 2011, 08:34
by knorke

Code: Select all

draw=0
up=0
function widget:DrawScreen()
  draw=draw+1
  Spring.Echo ("DrawScreen " .. draw)
end
function widget:Update()
  up=up+1
  Spring.Echo ("Update" .. up)
end
numbers always count up stay in sync.
at least in unsynced widget.

Still there must be something better, some command related callins?
http://springrts.com/wiki/LuaCallinReturnsadly this page sucks.
CommandsChanged() maybe?

Re: Persistent build spacing

Posted: 09 Sep 2011, 11:16
by DrHash
Indeed, gui_persistent_build_spacing.lua requires only to catch 2 events:

- The player has clicked on a building icon or pressed a building keystroke -> change buildspacing to the last stored value for the selected building.

- The player has finished to lay out the building -> store the current buildspacing value for this building.

The second case could be easily handled by means of this function:

Code: Select all

function widget:CommandNotify(id, params, options)
  -- If a building command has just been issued
  if (id < 0) then
    -- Get command name for better readability
    local _, _, _, commandName = GetActiveCommand()
    -- Store current buildspacing for this building command
    buildSpacingTable[commandName] = GetBuildSpacing()
  end
end
The only difference would be that buildspacing would only be stored once the building is laid out, while in the current version of the widget we may cancel the building command but changes made in buildspacing would have been stored. Well, not a big deal.

However, I have not seen a specific callin function for the first case, so I used DrawScreen as done in widget gui_context_build.lua (http://widgets.springrts.de/index.php#134)

Re: Persistent build spacing

Posted: 09 Sep 2011, 12:11
by SirMaverick
FLOZi wrote:I was told fairly recently that Update() is called for every draw frame (a way to keep draw callins to only OGL calls)
That's the case. Either believe knorke's empirical study or read the source code (SpringApp.cpp).

Re: Persistent build spacing

Posted: 09 Sep 2011, 12:24
by Niobium
Really cool idea. I didn't like the complexity of the widget though, so I recoded it, stripping out the unnecessary parts.

Code: Select all

-- Config
local defaultSpacing = 0

-- Globals
local lastCmdID = nil
local buildSpacing = {}

-- Speedups
local spGetActiveCommand = Spring.GetActiveCommand
local spGetBuildSpacing = Spring.GetBuildSpacing
local spSetBuildSpacing = Spring.SetBuildSpacing

-- Callins
function widget:Update()
    
    local _, cmdID = spGetActiveCommand()
    if cmdID and cmdID < 0 then
        
        if cmdID ~= lastCmdID then
            spSetBuildSpacing(buildSpacing[-cmdID] or defaultSpacing)
            lastCmdID = cmdID
        end
        
        buildSpacing[-cmdID] = spGetBuildSpacing()
    end
end

function widget:GetConfigData()
    return { buildSpacing = buildSpacing }
end

function widget:SetConfigData(data)
    buildSpacing = data.buildSpacing or {}
end

Re: Persistent build spacing

Posted: 09 Sep 2011, 12:42
by very_bad_soldier
If you want to go that way you could even save one level of indentation by replacing

Code: Select all

function widget:Update()
    
    local _, cmdID = spGetActiveCommand()
    if cmdID and cmdID < 0 then
        
        if cmdID ~= lastCmdID then
            spSetBuildSpacing(buildSpacing[-cmdID] or defaultSpacing)
            lastCmdID = cmdID
        end
        
        buildSpacing[-cmdID] = spGetBuildSpacing()
    end
end
by (untested)

Code: Select all

function widget:Update()
    
    local _, cmdID = spGetActiveCommand()
    if not cmdID or cmdID >= 0 then
        return
    end
        
    if cmdID ~= lastCmdID then
        spSetBuildSpacing(buildSpacing[-cmdID] or defaultSpacing)
        lastCmdID = cmdID
    end
        
    buildSpacing[-cmdID] = spGetBuildSpacing()
end
Care to explain this?

Code: Select all

function widget:GetConfigData()
    return { buildSpacing = buildSpacing }
end
To be able to add further configuration table entries later?