Several changes

- Don't change effects, as it's not needed anymore
- Add capture library to be loaded by lua (go and PureBasic)
- Modify pixel size, to nicely fit into FullHD
- Move camera instead of player
- Add capturing routine
- Round position to fit into grid, and also render the world pixel perfect
- Add util/helper functions
This commit is contained in:
David Vogel 2019-10-20 16:28:17 +02:00
parent be48a75c25
commit 99ad91e67c
21 changed files with 565 additions and 24 deletions

34
.gitignore vendored
View File

@ -1,7 +1,28 @@
# File created using '.gitignore Generator' for Visual Studio Code: https://bit.ly/vscode-gig
# Created by https://www.gitignore.io/api/visualstudiocode,windows,lua
# Edit at https://www.gitignore.io/?templates=visualstudiocode,windows,lua
# Created by https://www.gitignore.io/api/lua,visualstudiocode,windows,go
# Edit at https://www.gitignore.io/?templates=lua,visualstudiocode,windows,go
### Go ###
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib
# Test binary, built with `go test -c`
*.test
# Output of the go coverage tool, specifically when used with LiteIDE
*.out
# Dependency directories (remove the comment below to include it)
# vendor/
### Go Patch ###
/vendor/
/Godeps/
### Lua ###
# Compiled Lua sources
@ -32,14 +53,9 @@ luac.out
*.exp
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
@ -83,7 +99,9 @@ $RECYCLE.BIN/
# Windows shortcuts
*.lnk
# End of https://www.gitignore.io/api/visualstudiocode,windows,lua
# End of https://www.gitignore.io/api/lua,visualstudiocode,windows,go
# Custom rules (everything added below won't be overriden by 'Generate .gitignore File' if you use 'Update' option)
/libs/
output/

View File

@ -1,5 +1,7 @@
{
"cSpell.words": [
"Vogel"
"Vogel",
"kbinani",
"noita"
]
}

95
bin/capture-b/Capture.pb Normal file
View File

@ -0,0 +1,95 @@
UsePNGImageEncoder()
Declare Worker(*Dummy)
Structure QueueElement
img.i
x.i
y.i
EndStructure
ProcedureDLL AttachProcess(Instance)
Global Semaphore = CreateSemaphore()
Global Mutex = CreateMutex()
Global NewList Queue.QueueElement()
For i = 1 To 4
CreateThread(@Worker(), #Null)
Next
EndProcedure
Procedure Worker(*Dummy)
Protected img, x, y
Repeat
WaitSemaphore(Semaphore)
LockMutex(Mutex)
FirstElement(Queue())
img = Queue()\img
x = Queue()\x
y = Queue()\y
DeleteElement(Queue())
UnlockMutex(Mutex)
SaveImage(img, "mods/noita-mapcap/output/" + x + "," + y + ".png", #PB_ImagePlugin_PNG)
FreeImage(img)
ForEver
EndProcedure
ProcedureDLL Capture(px.i, py.i)
; Get dimensions of main screen
ExamineDesktops()
x = DesktopX(0)
y = DesktopY(0)
w = DesktopWidth(0)
h = DesktopHeight(0)
imageID = CreateImage(#PB_Any, w, h)
If Not imageID
ProcedureReturn
EndIf
; Get DC of whole screen
screenDC = GetDC_(#Null)
hDC = StartDrawing(ImageOutput(imageID))
If Not hDC
ReleaseDC_(#Null, screenDC)
FreeImage(imageID)
ProcedureReturn
EndIf
BitBlt_(hDC, 0, 0, w, h, screenDC, x, y, #SRCCOPY)
StopDrawing()
ReleaseDC_(#Null, screenDC)
LockMutex(Mutex)
; Check if the queue has too many elements, if so, wait. (Simulate go's channels)
While ListSize(Queue()) > 0
UnlockMutex(Mutex)
Delay(10)
LockMutex(Mutex)
Wend
LastElement(Queue())
AddElement(Queue())
Queue()\img = imageID
Queue()\x = px
Queue()\y = py
UnlockMutex(Mutex)
SignalSemaphore(Semaphore)
EndProcedure
; IDE Options = PureBasic 5.71 LTS (Windows - x64)
; ExecutableFormat = Shared dll
; CursorPosition = 15
; Folding = -
; EnableThread
; EnableXP
; Executable = capture.dll
; DisableDebugger
; Compiler = PureBasic 5.71 LTS (Windows - x86)

7
bin/capture-b/README.md Normal file
View File

@ -0,0 +1,7 @@
# Capture
A dynamically linked library, that supplies a single function to take a screenshot and store it as PNG file.
## State
Works, not as fast as the go version, but ok.

7
bin/capture/README.md Normal file
View File

@ -0,0 +1,7 @@
# Capture
A dynamically linked library, that supplies a single function to take a screenshot and store it as PNG file.
## State
Works, but it seems the go runtime interferes with other stuff inside noita. Therefore it's unusable, because it causes crashes.

9
bin/capture/build.bat Normal file
View File

@ -0,0 +1,9 @@
rem Copyright (c) 2019 David Vogel
rem
rem This software is released under the MIT License.
rem https://opensource.org/licenses/MIT
set GOARCH=386
set CGO_ENABLED=1
go build -o capture.dll -buildmode=c-shared

View File

@ -4,3 +4,83 @@
// https://opensource.org/licenses/MIT
package main
import "C"
import (
"fmt"
"image"
"image/png"
"os"
"github.com/kbinani/screenshot"
)
type encodeElement struct {
x, y int
img *image.RGBA
}
var encodeQueue = make(chan encodeElement)
var bounds = screenshot.GetDisplayBounds(0) // Only care about the main screen
func init() {
// Start encode workers
startWorkers()
}
func main() {
/*flagX := flag.Int("x", 0, "x coordinate")
flagY := flag.Int("y", 0, "y coordinate")
flag.Parse()
Capture(*flagX, *flagY)
//startServer()*/
/*for i := 0; i < 5000; i++ {
Capture(i, 0)
}*/
}
func startWorkers() {
for i := 0; i < 8; i++ {
go func() {
encoder := png.Encoder{CompressionLevel: png.BestSpeed}
for elem := range encodeQueue {
fileName := fmt.Sprintf("mods/noita-mapcap/output/%d,%d.png", elem.x, elem.y)
//fileName := fmt.Sprintf("%d,%d.png", x, y)
file, err := os.Create(fileName)
if err != nil {
continue
}
encoder.Encode(file, elem.img)
file.Close()
}
}()
}
}
//Capture creates a snapshot of the whole main screen, and stores it inside the mod's output folder.
//export Capture
func Capture(x, y int) {
img, err := screenshot.CaptureRect(bounds)
if err != nil {
panic(err)
//return
}
//img := image.NewRGBA(image.Rect(0, 0, 1920, 1080))
encodeQueue <- encodeElement{
img: img,
x: x,
y: y,
}
}

78
bin/capture/capture.h Normal file
View File

@ -0,0 +1,78 @@
/* Code generated by cmd/cgo; DO NOT EDIT. */
/* package github.com/Dadido3/noita-mapcap/bin/capture */
#line 1 "cgo-builtin-export-prolog"
#include <stddef.h> /* for ptrdiff_t below */
#ifndef GO_CGO_EXPORT_PROLOGUE_H
#define GO_CGO_EXPORT_PROLOGUE_H
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef struct { const char *p; ptrdiff_t n; } _GoString_;
#endif
#endif
/* Start of preamble from import "C" comments. */
/* End of preamble from import "C" comments. */
/* Start of boilerplate cgo prologue. */
#line 1 "cgo-gcc-export-header-prolog"
#ifndef GO_CGO_PROLOGUE_H
#define GO_CGO_PROLOGUE_H
typedef signed char GoInt8;
typedef unsigned char GoUint8;
typedef short GoInt16;
typedef unsigned short GoUint16;
typedef int GoInt32;
typedef unsigned int GoUint32;
typedef long long GoInt64;
typedef unsigned long long GoUint64;
typedef GoInt32 GoInt;
typedef GoUint32 GoUint;
typedef __SIZE_TYPE__ GoUintptr;
typedef float GoFloat32;
typedef double GoFloat64;
typedef float _Complex GoComplex64;
typedef double _Complex GoComplex128;
/*
static assertion to make sure the file is being used on architecture
at least with matching size of GoInt.
*/
typedef char _check_for_32_bit_pointer_matching_GoInt[sizeof(void*)==32/8 ? 1:-1];
#ifndef GO_CGO_GOSTRING_TYPEDEF
typedef _GoString_ GoString;
#endif
typedef void *GoMap;
typedef void *GoChan;
typedef struct { void *t; void *v; } GoInterface;
typedef struct { void *data; GoInt len; GoInt cap; } GoSlice;
#endif
/* End of boilerplate cgo prologue. */
#ifdef __cplusplus
extern "C" {
#endif
//Capture creates a snapshot of the whole main screen, and stores it inside the mod's output folder.
extern void Capture(GoInt p0, GoInt p1);
#ifdef __cplusplus
}
#endif

View File

@ -0,0 +1,81 @@
// Copyright (c) 2019 David Vogel
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
package main
import (
"bufio"
"log"
"net"
"strconv"
"strings"
)
func startServer() {
log.Printf("Launching server...\n")
// listen on all interfaces
ln, err := net.Listen("tcp", ":46789")
if err != nil {
log.Fatalf("Couldn't open TCP server at port 46789: %v", err)
}
// accept connections
for {
conn, err := ln.Accept()
if err != nil {
log.Fatalf("Couldn't accept TCP connection: %v", err)
}
// Handle incoming data
go func(conn net.Conn) {
defer conn.Close()
var x, y int
for {
message, err := bufio.NewReader(conn).ReadString('\n')
if err != nil {
log.Printf("Couldn't read from connection: %v", err)
return
}
// Check for end condition
if message == "\n" {
break
}
colonPos := strings.IndexByte(message, ':') // Expect UTF-8
if colonPos < 0 {
log.Printf("Missing colon in message: %v", message)
return
}
key, value := message[:colonPos], message[colonPos+1:]
log.Printf("Bla %v blub %v", key, value)
switch key { // Expect UTF-8
case "x":
if res, err := strconv.ParseInt(value, 10, 0); err == nil {
x = int(res)
} else {
log.Printf("Can't parse string %v to integer: %v", value, err)
return
}
case "y":
if res, err := strconv.ParseInt(value, 10, 0); err == nil {
y = int(res)
} else {
log.Printf("Can't parse string %v to integer: %v", value, err)
return
}
}
}
conn.Write([]byte("\n"))
log.Printf("capture bla at %v, %v", x, y)
}(conn)
}
}

View File

@ -0,0 +1,17 @@
<Entity >
<FogOfWarRadiusComponent _enabled="0"
radius="200">
</FogOfWarRadiusComponent>
<SpriteComponent _enabled="0"
alpha="0.8"
image_file="data/particles/torch_fog_of_war_hole.xml"
smooth_filtering="1"
fog_of_war_hole="1"
has_special_scale="1"
special_scale_x="4"
special_scale_y="4">
</SpriteComponent>
</Entity>

View File

@ -3,12 +3,19 @@
-- This software is released under the MIT License.
-- https://opensource.org/licenses/MIT
dofile("data/scripts/lib/coroutines.lua")
dofile("mods/noita-mapcap/files/util.lua")
dofile("mods/noita-mapcap/files/external.lua")
dofile("mods/noita-mapcap/files/ws.lua")
--dofile("data/scripts/lib/utilities.lua")
dofile("data/scripts/perks/perk_list.lua")
local CAPTURE_GRID_SIZE = 64 -- in ingame pixels
local CAPTURE_DELAY = 30 -- in frames
if not async then -- Check if lib is already loaded
dofile("data/scripts/lib/coroutines.lua")
end
local CAPTURE_GRID_SIZE = 128 -- in ingame pixels
local CAPTURE_DELAY = 15 -- in frames
local CAPTURE_FORCE_HP = 40 -- * 25HP
local function getPlayer()
@ -87,20 +94,14 @@ local function resetPlayer()
setPlayerHP(CAPTURE_FORCE_HP)
end
local function doCapture()
local function startCapturing()
local ox, oy = getPlayerPos()
ox, oy = math.floor(ox / CAPTURE_GRID_SIZE) * CAPTURE_GRID_SIZE, math.floor(oy / CAPTURE_GRID_SIZE) * CAPTURE_GRID_SIZE
local x, y = ox, oy
preparePlayer()
-- Coroutine to force player to x, y coordinate
async_loop(
function()
teleportPlayer(x, y)
resetPlayer()
wait(0)
end
)
GameSetCameraFree(true)
-- Coroutine to calculate next coordinate, and trigger screenshots
local i = 1
@ -108,23 +109,31 @@ local function doCapture()
function()
-- +x
for i = 1, i, 1 do
TriggerCapture(x, y)
x, y = x + CAPTURE_GRID_SIZE, y
GameSetCameraPos(x, y)
wait(CAPTURE_DELAY)
end
-- +y
for i = 1, i, 1 do
TriggerCapture(x, y)
x, y = x, y + CAPTURE_GRID_SIZE
GameSetCameraPos(x, y)
wait(CAPTURE_DELAY)
end
i = i + 1
-- -x
for i = 1, i, 1 do
TriggerCapture(x, y)
x, y = x - CAPTURE_GRID_SIZE, y
GameSetCameraPos(x, y)
wait(CAPTURE_DELAY)
end
-- -y
for i = 1, i, 1 do
TriggerCapture(x, y)
x, y = x, y - CAPTURE_GRID_SIZE
GameSetCameraPos(x, y)
wait(CAPTURE_DELAY)
end
i = i + 1
@ -143,12 +152,19 @@ async_loop(
GuiLayoutBeginVertical(gui, 50, 20)
if GuiButton(gui, 0, 0, "Start capturing map", 1) then
doCapture()
startCapturing()
GuiDestroy(gui)
gui = nil
end
GuiTextCentered(gui, 0, 0, "Don't do anything while the capturing process is running!")
GuiTextCentered(gui, 0, 0, "Use ESC and close the game to stop the process.")
--[[if GuiButton(gui, 0, 0, "DEBUG globals", 1) then
local file = io.open("mods/noita-mapcap/output/globals.txt", "w")
for i, v in pairs(_G) do
file:write(i .. "\n")
end
file:close()
end]]
GuiLayoutEnd(gui)
end

67
files/external.lua Normal file
View File

@ -0,0 +1,67 @@
-- Copyright (c) 2019 David Vogel
--
-- This software is released under the MIT License.
-- https://opensource.org/licenses/MIT
--local host, port = "127.0.0.1", "46789"
--local socket = require("socket")
--local tcp = assert(socket.tcp())
--package.path = package.path .. ";" .. "mods/noita-mapcap/libs/lua/" .. "?.lua"
--package.cpath = package.cpath .. ";" .. "mods/noita-mapcap/libs/" .. "?.dll" -- TODO: Make it OS aware
local ffi = ffi or _G.ffi or require("ffi")
-- Don't let lua garbage collect and unload the lib, it will cause crashes later on!
-- Blah, this causes random crashes anyways. Probably the go runtime that interferes with something else in noita.
local status, caplib = pcall(ffi.load, "mods/noita-mapcap/bin/capture-b/capture")
if not status then
print("Error loading capture lib: " .. cap)
end
ffi.cdef [[
void Capture(int x, int y);
]]
function TriggerCapture(x, y)
--IngamePrint(os.execute(string.format("mods/noita-mapcap/bin/capture/capture.exe -x %i -y %i", x, y)))
--IngamePrint("a", os.execute("capture.exe"))
--os.execute("screenshots")
--IngamePrint("b", os.execute("../bin/capture/capture.exe"))
--local handle = io.popen("mods/noita-mapcap/bin/capture/capture.exe")
--local result = handle:read("*a")
--IngamePrint(result)
--handle:close()
--IngamePrint(os.execute("echo test"))
--print("trace", "A")
caplib.Capture(x, y)
--print("trace", "B")
end
--[[function TriggerCapture(x, y)
local status, lib = pcall(require, "socket")
if not status then
IngamePrint("Error loading socket lib: " .. lib)
end
IngamePrint("DEBUG - Capture")
tcp:connect(host, port)
IngamePrint("DEBUG - Connected")
tcp:send(string.format("x: %i\n", x))
tcp:send(string.format("y: %i\n", y))
tcp:send(string.format("\n", y))
IngamePrint("DEBUG - Sent")
local result, error = tcp:receive("*l")
-- Ignore error or result for now, the function blocks until a newline character is received.
IngamePrint("DEBUG - Received")
tcp:close()
IngamePrint("DEBUG - Closed")
end]]

View File

@ -1,5 +1,5 @@
<MagicNumbers VIRTUAL_RESOLUTION_X="427"
VIRTUAL_RESOLUTION_Y="242"
<MagicNumbers VIRTUAL_RESOLUTION_X="960"
VIRTUAL_RESOLUTION_Y="540"
CAMERA_NO_MOVE_BUFFER_NEAR_VIEWPORT_EDGE="0.0"
CAMERA_MOUSE_INTERPOLATION_SPEED="0.0"
CAMERA_POSITION_INTERPOLATION_SPEED="50.0"

44
files/util.lua Normal file
View File

@ -0,0 +1,44 @@
-- Copyright (c) 2019 David Vogel
--
-- This software is released under the MIT License.
-- https://opensource.org/licenses/MIT
function splitStringByLength(string, length)
local chunks = {}
for i = 1, #string, length do
table.insert(chunks, string:sub(i, i + length - 1))
end
return chunks
end
-- Improved version of GamePrint, that behaves more like print.
function IngamePrint(...)
local arg = {...}
local result = ""
for i, v in ipairs(arg) do
result = result .. tostring(v) .. " "
end
for line in result:gmatch("[^\r\n]+") do
for i, v in ipairs(splitStringByLength(line, 100)) do
GamePrint(v)
end
end
end
-- Globally overwrite print function and write output into a logfile
local logFile = io.open("lualog.txt", "w")
function print(...)
local arg = {...}
local result = ""
for i, v in ipairs(arg) do
result = result .. tostring(v) .. "\t"
end
result = result .. "\n"
logFile:write(result)
logFile:flush()
end

10
go.mod Normal file
View File

@ -0,0 +1,10 @@
module github.com/Dadido3/noita-mapcap
go 1.13
require (
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 // indirect
github.com/gen2brain/shm v0.0.0-20180314170312-6c18ff7f8b90 // indirect
github.com/kbinani/screenshot v0.0.0-20190719135742-f06580e30cdc
github.com/lxn/win v0.0.0-20190919090605-24c5960b03d8 // indirect
)

10
go.sum Normal file
View File

@ -0,0 +1,10 @@
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802 h1:1BDTz0u9nC3//pOCMdNH+CiXJVYJh5UQNCOBG7jbELc=
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/gen2brain/shm v0.0.0-20180314170312-6c18ff7f8b90 h1:QagTG5rauLt6pVVEhnVSrlIX4ifhVIZOwmw6x6D8TUw=
github.com/gen2brain/shm v0.0.0-20180314170312-6c18ff7f8b90/go.mod h1:uF6rMu/1nvu+5DpiRLwusA6xB8zlkNoGzKn8lmYONUo=
github.com/kbinani/screenshot v0.0.0-20190719135742-f06580e30cdc h1:kGFotla6Dyr6a2ILeExAHlttPgJtnoP/GIw2uVN/4h4=
github.com/kbinani/screenshot v0.0.0-20190719135742-f06580e30cdc/go.mod h1:f8GY5V3lRzakvEyr49P7hHRYoHtPr8zvj/7JodCoRzw=
github.com/lxn/win v0.0.0-20190919090605-24c5960b03d8 h1:RVMGIuuNgrpGB7I79f6xfhGCkpN47IaEGh8VTM0p7Xc=
github.com/lxn/win v0.0.0-20190919090605-24c5960b03d8/go.mod h1:ouWl4wViUNh8tPSIwxTVMuS014WakR1hqvBc2I0bMoA=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd h1:DBH9mDw0zluJT/R+nGuV3jWFWLFaHyYZWD4tOT+cjn0=
golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=