Skip to content

Commit

Permalink
Big update
Browse files Browse the repository at this point in the history
- Drop support for versions < 1.13, as I don't want to install that many versions of d2 to get the offsets
- Add config with a few options
- Add support for ignoring time spent in town past a certain threshold, to allow for stash management without ruining xp/min (default is 20 seconds)
- Time spent paused is now ignored for game-time xp/min calculations
- Add run tracking and # runs till level
- Reformat console output to be more readable/concise
- Add /players x value to output
- General improvements/bug fixes/refactoring
- Add a bunch of stuff for calculating real xp gain (area level, player level pentalty, /players x), but don't have a good way of displaying it yet and its not quite done (relevant console output is disabled by default)
- Now depends on luabitop
  • Loading branch information
squeek502 committed Feb 23, 2018
1 parent 7060818 commit 4073f7e
Show file tree
Hide file tree
Showing 12 changed files with 717 additions and 122 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
output
d2info-config.lua
d2info-config-default.lua

# Created by https://www.gitignore.io/api/lua,linux,windows

Expand Down
49 changes: 26 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,26 @@ Currently, it provides the following information:

- Experience per minute over different time periods:
- Real time (since the character was first seen by the d2info process)
- In-game time (only includes time that the character is in a game)
- In-game time (only includes time that the character is in a game, not paused, etc)
- Current game
- Last game (xp/min of the last game at the point of save+quit)
- Estimated time until the next level, using the various exp/min readings
- Number of runs finished, average xp/min per run, estimated runs until the next level
- Number of experience 'ticks' gained (pixels filled in the experience bar)
- Information about the current area, like monster level and % xp gain (unfinished, disabled by default; see `SHOW_AREA_INFORMATION` in the config)

Supports Diablo II verisons 1.11, 1.11b, 1.12, 1.13c, 1.13d, 1.14c, and 1.14d
Supports Diablo II verisons 1.13c, 1.13d, 1.14b, 1.14c, and 1.14d

## Installation

Simply grab the [latest .exe build from the releases page](https://github.com/squeek502/d2info/releases/latest) and run it.

*Note: You'll probably want to put the .exe in its own folder, as it will output various files relative to its location*

## Running using Lua

- Clone this repository
- Build [memreader](https://github.com/squeek502/memreader), [sleep](https://github.com/squeek502/sleep), and [luafilesystem](https://github.com/keplerproject/luafilesystem) and make the resulting .dll's available to Lua's `package.cpath`.
- Build [memreader](https://github.com/squeek502/memreader), [sleep](https://github.com/squeek502/sleep), [luafilesystem](https://github.com/keplerproject/luafilesystem), and [LuaBitOp](http://bitop.luajit.org/) and make the resulting .dll's available to Lua's `package.cpath`.
- Run `lua d2info.lua`

## Output
Expand All @@ -32,26 +36,25 @@ The information is output to both the console window and to individual text file

Console output example:
```
CoolGuy
Overall (real-time): 167.3k xp/min
Overall (game-time): 183.2k xp/min
Current game: 87.5k xp/min
Last game: 258.7k xp/min
Est time until level 96:
12h (using real-time xp/min)
11h (using game-time xp/min)
23h (using current game's xp/min)
8h22m (using last game's xp/min)
Exp gained (overall): 2.4m
Exp gained (current game): 179.8k
Exp gained (last game): 156.1k
Ticks gained (overall): 1.4
Ticks gained (current game): 0.1
Ticks gained (last game): 0.1
CharName (level 96 & 32.85%)
/players 8
Run #6:
207.8k xp/min (734.2k xp in 3m32s)
13h until level 97 at this rate
Last run:
160.7k xp/min (576.0k xp in 3m35s)
17h until level 97 at this rate
Average run:
165.0k xp/min (521.0k xp in 3m09s)
315 runs until level 97
This session:
+1.6 ticks (+1.36%)
177.8k xp/min (3.3m xp in 19m19s)
15h until level 97 at this rate
```

## Acknowledgements
Expand Down
55 changes: 8 additions & 47 deletions d2info.lua
Original file line number Diff line number Diff line change
@@ -1,57 +1,18 @@
local memreader = require('memreader')
local D2Reader = require('d2info.d2reader')
local sleep = require('sleep')
local Session = require('d2info.session')
local D2Reader = require('d2info.d2reader')
local Output = require('d2info.output')
local Config = require('d2info.config')
local GameState = require('d2info.gamestate')

memreader.debugprivilege(true)
local reader = D2Reader.new()
local sessions = {}
local output = Output.new()
local lastInfo = {}
local UPDATE_PERIOD = 1000

while true do
local player = reader:getPlayerName()
local exp, lvl = reader:getExperience()
if player and exp then
if not sessions[player] then
sessions[player] = {}
sessions[player].total = Session.new(exp, lvl)
end
if sessions[player].current == nil then
sessions[player].current = Session.new(exp, lvl)
end

local current, total, last = sessions[player].current, sessions[player].total, sessions[player].last
current:update(exp, lvl)
total:update(exp, lvl)
local reader, output, config = D2Reader.new(), Output.new(), Config.new()
local state = GameState.new(reader, config, output)

output:toScreen(player, lvl, total, current, last)
output:toFile(player, lvl, total, current, last)
local UPDATE_PERIOD = config:get("UPDATE_PERIOD")

current:incrementDuration()
total:incrementDuration()

lastInfo.player = player
lastInfo.level = lvl
elseif reader.status ~= nil then
os.execute('cls')
print(reader.status)
else
os.execute('cls')
print("No player")

if lastInfo.player ~= nil and sessions[lastInfo.player].current ~= nil then
sessions[lastInfo.player].last = sessions[lastInfo.player].current
sessions[lastInfo.player].current = nil
end

-- need to update files here because otherwise they wouldn't update
-- while at the menu screen during save+quit
if lastInfo.player then
output:toFile(lastInfo.player, lastInfo.level, sessions[lastInfo.player].total, sessions[lastInfo.player].current, sessions[lastInfo.player].last)
end
end
while true do
state:tick(UPDATE_PERIOD)
sleep(UPDATE_PERIOD)
end
97 changes: 97 additions & 0 deletions d2info/config.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
local lfs = require('lfs')

local DEFAULT_CONFIG_NOTE = [=[--[[
NOTE: This file is not used by d2info and will get overwritten on every run of d2info.
Instead, it is intended to be used as a reference for updating your config
file after updating to a new version of d2info (to see new config options, etc).
]]--
]=]
local DEFAULT_CONFIG = [[
return {
-- time between updates, in milliseconds
UPDATE_PERIOD = 1000,
-- maximum duration spent in each town that will count towards exp/min calculations
-- (e.g. with a value of 30, spending longer than 30 seconds in any given town will only
-- count as 30 seconds when calculating exp/min using game time)
-- this allows for doing things like stash management without affecting the stats too much
MAX_TOWN_DURATION = 20,
-- enable/disable outputting info to the console screen
OUTPUT_TO_SCREEN = true,
-- enable/disable outputting info to files
OUTPUT_TO_FILE = true,
-- show information about the area you are currently in, such as: area level,
-- percentage xp gain from monsters/champions/uniques in that area, etc
SHOW_AREA_INFORMATION = false,
}
]]

local Config = {}
Config.__index = Config

function Config.new(file, defaultFile)
local self = setmetatable({}, Config)
self.default = assert(loadstring(DEFAULT_CONFIG))()
self.config = {}
self.file = file or "d2info-config.lua"
self.defaultFile = defaultFile or "d2info-config-default.lua"
self:load(file)
self:write(self.defaultFile, DEFAULT_CONFIG_NOTE .. DEFAULT_CONFIG)
return self
end

local function resolve(t, ...)
local keys = {...}
local key = table.remove(keys, 1)
if #keys == 0 then
return t[key]
end
if not t[key] then
return nil
end
return resolve(t[key], keys)
end

-- Gets a config value by resolving its keys in order
-- e.g. get('a', 'b') will return config['a']['b']
function Config:get(...)
local v = resolve(self.config, ...)
if v == nil then
v = resolve(self.default, ...)
end
return v
end

function Config:load(file)
if not file then file = self.file end
if not self:exists(file) then
self:write(file, DEFAULT_CONFIG)
return
end
local f = assert(io.open(file))
local str = f:read("*all")
local fn = assert(loadstring(str, file))
local loaded = fn()
self.config = loaded
end

function Config:write(file, data)
if not file then file = self.file end
local f = assert(io.open(file, "w"))
f:write(data)
f:close()
end

function Config:exists(file)
if not file then file = self.file end
return lfs.attributes(file) ~= nil
end

return Config
Loading

0 comments on commit 4073f7e

Please sign in to comment.