Skip to content

Commit

Permalink
Add Lua support (#5)
Browse files Browse the repository at this point in the history
* working lua integration

* documented lua interface

* binding lua functions to keys

* keybinds and some basic scripts

* actual docs for lua pogg

* add example lua init script

* update readme to include some basics of lua

* small docs tweak

* re-enable perf timing with a ring buffer

* some more nits

* some dead code

* fixes http request error by setting starting seed to actual time

* update readme to include vscode lsp extension

* ready to launch!
  • Loading branch information
Underscore76 authored Sep 16, 2023
1 parent d217516 commit f0fbc4b
Show file tree
Hide file tree
Showing 52 changed files with 5,967 additions and 35 deletions.
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -402,4 +402,7 @@ ASALocalRun/
.mfractor/

# Local History for Visual Studio
.localhistory/
.localhistory/

# vscode specific folder
.vscode/
71 changes: 71 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Stardew Valley TAS Mod
[Build Issues?](#build-issues)

NOTE: Use of this mod will create a folder `StardewTAS` in your `Documents` folder. This folder will contain your save states, scripts, screenshots, etc.

## Basic controls (for this alpha)
Basic Framework for a TAS Mod, includes
* frame advance (press `q` or `downarrow` for 1 frame, hold `space` for real time)
Expand All @@ -18,6 +20,57 @@ Slowly copying over/making sure things work in the new system.

Console supports scrolling, selection, normal copy and paste (or it should, LET ME KNOW because it works on mac fine). Console font has no support for non-ascii, so will print `?`.

### Lua Support

Scripting in this TAS is done with Lua! By typing `lua` onto the console you'll enter into a Lua REPL (read-eval-print-loop), which will allow you to run arbitrary lua code. Documentation for the core lua engine functions can be found by navigating locally to docs/ldoc/index.html or you can browse the `TASMod/Assets/lua` folder where the lua files are stored.

There is an example `init.lua` file in the `lua-examples` folder. The TASMod will look for a file called `init.lua` in the `StardewTAS/Scripts/` folder and will run that file when first launching into the lua console. This allows you to define custom functions and aliases that you can use in the console. For example, you can define a function called `myfunc` in `init.lua` and then call it from the lua console with `myfunc()`. You can also do things like auto-load into a specific save state or configure the engine state by toggling overlays/logic etc (or loading a particular engine state).

#### Visual Studio Code Support
If you are using Visual Studio Code for editing files in your local `StardewTAS/Scripts` folder, I recommend installing the [Lua language server](https://marketplace.visualstudio.com/items?itemName=sumneko.lua) extension to get type annotations and autocomplete for Lua. To include all the base engine lua files, open the workspace settings (Shift + Command + P (Mac) / Ctrl + Shift + P (Windows/Linux) to open the command palette) and search for `lua.workspace.library`, click Add Item, and then paste the path to the `lua` folder from the mod install.

Default Steam install paths (if you are on linux let me know the path!):
* Mac: `~/Library/Application Support/Steam/steamapps/common/Stardew Valley/Contents/MacOS/Mods/TASMod/assets/lua/`
* Windows: `C:\Program Files (x86)\Steam\steamapps\common\Stardew Valley\Mods\TASMod\assets\lua\`

You don't need to do this (the mod will correctly link the files), but it will give you intellisense for the base engine code.

#### Key Lua Helper
* `advance({keyboard={keys to press}, mouse={X=int, Y=int, left=bool, right=bool}})` - advance a frame with the given keyboard/mouse inputs. This should then pause while any existing automation is running. Example for holding down and right and clicking the mouse:
```lua
advance({keyboard={Keys.S, Keys.D}, mouse={X=100, Y=100, left=true}})
```

* Global frame stack
* `current_frame()` - returns the current frame actual frame
* `push(f)` - pushes a frame onto the frame stack
* `push()` or `gcf()` - pushes the current frame onto the frame stack
* `f = pop()` - pops a frame off the frame stack
* `rw()` - rewinds to the frame on the top of the frame stack
* `brw()` - blocking rewind to the frame on the top of the frame stack
* `frame_stack_clear()` - clears the frame stack
* `pgcf()` - print the global frame stack
* reset functions (blocking forces lua to wait until the reset is complete, others can occur async)
* `reset()` - reset to the current frame
* `freset()` - fast reset to the current frame
* `reset(f)` - reset to the given frame
* `freset(f)` - fast reset to the given frame
* `breset()` - blocking reset to the current frame
* `breset(f)` - blocking reset to the given frame
* `bfreset()` - blocking fast reset to the current frame
* `bfreset(f)` - blocking fast reset to the given frame
* save state functions
* `save()` - save the current state to file
* `saveas(f)` or `save(f)` - save the current state to a new file
* `load(f)` - load a save state and advance to final frame
* `fload(f)` - load a save state and fast advance to final frame
* `view()` - swap between view modes
* `exec("...")` - execute a command on the base console (e.g. `exec("overlay off Layers")`)

There's a ton more but there's a whole generated set of docs for the lua engine that you can find in the docs folder.

### Core Console Functions

#### Helper Functions:
Any function that is callable through this top level console has some help text associated with its use, and you can discover different tools through the list command.
* `help` - allows you to check command description/usage
Expand Down Expand Up @@ -134,3 +187,21 @@ At the bottom of the file you can add
```

(Thanks to @PianoAddict for finding these details!)


## Generating Lua Docs
This project uses [LDoc](https://github.com/lunarmodules/ldoc) to generate documentation for the lua files. To generate the docs, run the following command from the root directory of the project:

```bash
ldoc .
```

You'll need Lua/LuaRocks installed to run this command, which will vary by system. On Mac you can install Lua/LuaRocks with the following command:

```bash
brew install lua luarocks
luarocks install penlight
luarocks install ldoc
```

Looking for someone on windows to give guidance on how to install Lua/LuaRocks :).
72 changes: 72 additions & 0 deletions TASMod/Assets/lua/aliases.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
--- NOTE: this file pollutes the global namespace with common named elements
local console = require('core.console')

---swaps between different view states (base, map)
function view()
console.exec("view")
end

---save the current TAS inputs to the current file
---@param name string|nil @name of file to save to (default: nil)
function save(name)
if name ~= nil then
saveas(name)
return
end
console.exec("save")
end

---save the current TAS inputs to the specified file
---@param name string @name of file to save to
function saveas(name)
if name == nil then
print("ERROR: saveas requires a filename")
return
end
console.exec("saveas " .. name)
end

---load a TAS input file
---@param name string @name of file to load
function load(name)
if name == nil then
print("ERROR: load requires a filename")
return
end
if not save_state_exists(name) then
print("ERROR: save state does not exist")
return
end
console.exec("load " .. name)
end

---fast load a TAS input file
---@param name string @name of file to load
function fload(name)
if name == nil then
print("ERROR: fload requires a filename")
return
end
if not save_state_exists(name) then
print("ERROR: save state does not exist")
return
end
console.exec("fload " .. name)
end

---run the specified command on the top level console
---@param command string command to execute
function exec(command)
console.exec(command)
end

---check if a save state exists
---@param name string @name of the save state
---@return boolean @whether the save state exists
function save_state_exists(name)
if name == nil then
name = Controller.State.Prefix
end
local items = Controller.Console.Commands['ls']:GetFilePaths(RunCS(string.format('new string[]{"%s.json"}',name)))
return items.Count == 1
end
39 changes: 39 additions & 0 deletions TASMod/Assets/lua/core/clickables.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
-- clickables: a module for finding clickable objects in menus

local intro_click_map = require('core.data.click_maps')
local clickables = {}

---get a clickable object by name
---@param name string @the name of the object to get
function clickables.get_object_by_name(name)
local items = interface:GetClickableObjects()
if intro_click_map[name] ~= nil then
name = intro_click_map[name]
end
for i=0,items.Count-1
do
if items[i].Name == name then
return items[i]
elseif items[i].ID == name then
return items[i]
end
end
return nil
end

---check if a clickable object exists by name
---@param name string @the name of the object to check
function clickables.has_object(name)
return clickables.get_object_by_name(name) ~= nil
end

---list all clickable objects on screen
function clickables.list()
print_list(interface:GetClickableObjects(), (
function(x)
return x.Name
end
))
end

return clickables
105 changes: 105 additions & 0 deletions TASMod/Assets/lua/core/common.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
--- NOTE: this file pollutes the global namespace with common named elements

local engine = require('core.engine')
local frame_stack = require('core.utilities.frame_stack')

--
-- functions for working with the game engine interface
--

---advance the game by one frame
---@param input any
function advance(input)
engine.advance(input)
end

---wait for the game to halt logic
---@param max_frames number|nil
function halt(max_frames)
engine.halt(max_frames)
end

---advance the game by one frame
---@param frame number
function reset(frame)
engine.reset(frame)
end

---fast reset back to the specified frame
---@param frame number
function freset(frame)
engine.fast_reset(frame)
end

---blocking reset back to the specified frame
---@param frame number
function breset(frame)
engine.blocking_reset(frame)
end

---blocking fast reset back to the specified frame
---@param frame number
function bfreset(frame)
engine.blocking_fast_reset(frame)
end

---returns the current game frame
---@return number current frame number
function current_frame()
return interface:GetCurrentFrame()
end

--
-- functions for manipulating/working with the global framestack
--

---push the current frame to the global framestack
---@return number current frame number
function gcf()
return frame_stack.push(nil)
end

---print the global frame stack
function pgcf()
frame_stack.print()
end

---push a specific frame to the global frame stack
function push(f)
frame_stack.push(f)
end

---pop a frame from the global frame stack
---@return number|nil popped frame number
function pop()
return frame_stack.pop()
end

---fast reset back to the top of the global frame stack (NON-BLOCKING).
---for general resets this will work significantly faster than brw()
function rw()
frame_stack.rw()
end

---fast reset back to the top of the global frame stack (BLOCKING).
---this allows accurate syncing in a lua script to continue operating post-reset
function brw()
frame_stack.brw()
end

---clear the global frame stack
function frame_stack_clear()
frame_stack._X = {}
end

---gets the current real time
---@return System.DateTime @current real time
function real_time()
return DateTimeOffset.FromUnixTimeSeconds(os.time()):ToLocalTime()
end

-- stashing a raw mechanism to check if a file exists
-- function file_exists(name)
-- local f = io.open(name, "r")
-- return f ~= nil and io.close(f)
-- end
28 changes: 28 additions & 0 deletions TASMod/Assets/lua/core/console.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
--defines wrapper object around the TAS console

local console = {}

---closes the console
function console.close()
Controller.Console.openHeightTarget = 0
end

---opens the console
function console.open()
Controller.Console.openHeightTarget = 0.5
end

---plays back a function with the console closed
function console.playback(func)
Controller.Console.Close()
func()
Controller.Console.Open()
end

---executes a TAS console command. this is not the same as the SDV debug commands
---see docs for a list of commands or try `help` or `list`
function console.exec(command)
Controller.Console:RunCommand(command)
end

return console
23 changes: 23 additions & 0 deletions TASMod/Assets/lua/core/data/click_maps.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
-- raw click maps for the title screen/existing names

local title_click_map = {
NameBox = 536, FarmBox = 537, FavBox = 538,
Eye_H = 522, Eye_S = 523, Eye_V = 524,
Hair_H = 525, Hair_S = 526, Hair_V = 527,
Pants_H = 528, Pants_S = 529, Pants_V = 530,
Pet_Left = 511, Pet_Right = 510,

Shirt_Left = 512, Shirt_Right = 513,
Hair_Left = 514, Hair_Right = 515,
Acc_Left = "Acc_516", Acc_Right = "Acc_517",
Skin_Left = 518, Skin_Right = 519,
Pants_Left = 629, Pants_Right = 630,
_Left = 511, _Right = 510,

Male = 508, Female = 509,

Back = 81114, New = 81115, Load = 81116, Coop = 81119, Exit = 81117,
Standard = 531, Riverland = 532, Forest = 533, Hills = 534, Wilderness = 535, FourCorners = 545, Beach = 546,
Advanced = 636, OK = 505, SkipIntro = 506
}
return title_click_map
Loading

0 comments on commit f0fbc4b

Please sign in to comment.