Initial commit

master
Warren Ulrich 2023-06-10 00:53:11 -07:00
parent 6bf7e9c1fe
commit 0220cedf10
8 changed files with 431 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
build/
.vscode/

48
CMakeLists.txt Normal file
View File

@ -0,0 +1,48 @@
cmake_minimum_required(VERSION 3.26)
project(collision_cacher)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_SHARED_LINKER_FLAGS "-Os -s -m32 -static-libgcc -static-libstdc++ -static")
set(CMAKE_SHARED_LIBRARY_PREFIX "")
if (NOT DEFINED ALPACABOT_DIR)
set(ALPACABOT_DIR $ENV{USERPROFILE}\\AlpacaBot)
endif()
if (NOT DEFINED ALPACABOT_INCLUDE_DIR)
set(ALPACABOT_INCLUDE_DIR ${ALPACABOT_DIR}\\Include)
endif()
if (NOT DEFINED ALPACABOT_LIB_DIR)
set(ALPACABOT_LIB_DIR ${ALPACABOT_DIR}\\Library)
endif()
if (NOT DEFINED ALPACABOT_SCRIPT_DIR)
set(ALPACABOT_SCRIPT_DIR ${ALPACABOT_DIR}\\Script\\Local)
endif()
set(SOURCE_FILES
src/main.cpp
)
add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES})
set_target_properties(${PROJECT_NAME} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY ${ALPACABOT_SCRIPT_DIR}/${PROJECT_NAME}
)
target_include_directories(${PROJECT_NAME} PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/include
${ALPACABOT_INCLUDE_DIR}
)
target_link_directories(${PROJECT_NAME} PRIVATE
${ALPACABOT_LIB_DIR}
)
target_link_libraries(${PROJECT_NAME} PRIVATE
AlpacaLibrary
)

BIN
collision_map.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
collision_map_plane_0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

BIN
collision_map_plane_1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
collision_map_plane_2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

134
image_gen.go Normal file
View File

@ -0,0 +1,134 @@
package main
import (
"encoding/csv"
"image"
"image/color"
"image/png"
"log"
"os"
"path/filepath"
"strconv"
)
func main() {
// Path to the CSV file
userProfile := os.Getenv("USERPROFILE")
filePath := filepath.Join(userProfile, "AlpacaBot", "Collision Data", "collision_data.csv")
// Open the CSV file
file, err := os.Open(filePath)
if err != nil {
log.Fatalf("Failed to open file: %v", err)
}
defer file.Close()
// Read the CSV file
reader := csv.NewReader(file)
records, err := reader.ReadAll()
if err != nil {
log.Fatalf("Failed to read file: %v", err)
}
// Group records by plane
dataByPlane := make(map[string][][]string)
for _, record := range records[1:] { // Skip the header row
plane := record[1]
dataByPlane[plane] = append(dataByPlane[plane], record)
}
for plane, records := range dataByPlane {
// Find the min and max x, y coordinates
minX, minY, maxX, maxY := 1<<31-1, 1<<31-1, 0, 0
for _, record := range records {
x, err := strconv.Atoi(record[2])
if err != nil {
continue
}
y, err := strconv.Atoi(record[3])
if err != nil {
continue
}
if x < minX {
minX = x
}
if y < minY {
minY = y
}
if x > maxX {
maxX = x
}
if y > maxY {
maxY = y
}
}
// Create an empty image
width, height := maxX-minX+1, maxY-minY+1
img := image.NewRGBA(image.Rect(0, 0, width, height))
// Fill in the image with data
for _, record := range records {
x, err := strconv.Atoi(record[2])
if err != nil {
continue
}
y, err := strconv.Atoi(record[3])
if err != nil {
continue
}
flag, err := strconv.Atoi(record[4])
if err != nil {
continue
}
// Determine the color based on the flag value
var col color.Color
switch flag {
case 0:
col = color.RGBA{0, 255, 0, 255}
case 0xFFFFFF:
col = color.RGBA{255, 0, 0, 255}
case 0x1000000:
col = color.RGBA{0, 0, 255, 255}
case 0x100:
col = color.RGBA{255, 255, 0, 255}
case 0x20000:
col = color.RGBA{128, 0, 128, 255}
case 0x200000:
col = color.RGBA{255, 128, 0, 255}
case 0x2:
col = color.RGBA{0, 255, 255, 255}
case 0x8:
col = color.RGBA{255, 0, 255, 255}
case 0x20:
col = color.RGBA{128, 128, 0, 255}
case 0x80:
col = color.RGBA{128, 128, 128, 255}
case 0x4:
col = color.RGBA{0, 128, 0, 255}
case 0x10:
col = color.RGBA{128, 0, 0, 255}
case 0x40:
col = color.RGBA{0, 0, 128, 255}
case 0x1:
col = color.RGBA{0, 128, 128, 255}
default:
col = color.RGBA{0, 0, 0, 255}
}
// Set the pixel in the image
img.Set(x-minX, height-(y-minY)-1, col)
}
// Save the image to a file
outputFileName := "collision_map_plane_" + plane + ".png"
outputFile, err := os.Create(outputFileName)
if err != nil {
log.Fatalf("Failed to create output file: %v", err)
}
defer outputFile.Close()
png.Encode(outputFile, img)
}
}

247
src/main.cpp Normal file
View File

@ -0,0 +1,247 @@
#include <filesystem>
#include <fstream>
#include <iostream>
#include <unordered_set>
#include <Game/Core.hpp>
void Setup() {
ScriptInfo Info;
Info.Name = "Collision Cacher";
Info.Description = "Caches collision data around the player.";
Info.Version = "1.00";
Info.Category = "Magic";
Info.Author = "Warren";
Info.UID = "UID";
Info.ForumPage = "forum.alpacabot.org";
SetScriptInfo(Info);
}
class region_key {
public:
std::int32_t region;
std::int32_t plane;
region_key() = default;
region_key(std::int32_t region, std::int32_t plane)
: region(region), plane(plane) {}
bool operator==(const region_key& other) const {
return region == other.region && plane == other.plane;
}
};
class collision {
public:
Tile tile;
Pathfinding::COLLISION_FLAG flag;
collision(const Tile& tile, Pathfinding::COLLISION_FLAG flag) : tile(tile), flag(flag) {}
bool operator==(const collision& other) const {
return tile == other.tile && flag == other.flag;
}
};
namespace std {
template<>
struct hash<region_key> {
std::size_t operator()(const region_key& key) const {
std::size_t h1 = std::hash<std::int32_t>()(key.region);
std::size_t h2 = std::hash<std::int32_t>()(key.plane);
return h1 ^ (h2 << 1);
}
};
template<>
struct hash<Tile> {
std::size_t operator()(const Tile& tile) const {
std::size_t h1 = std::hash<std::int32_t>()(tile.X);
std::size_t h2 = std::hash<std::int32_t>()(tile.Y);
std::size_t h3 = std::hash<std::int32_t>()(tile.Plane);
return h1 ^ (h2 << 1) ^ (h3 << 2);
}
};
template<>
struct hash<collision> {
std::size_t operator()(const collision& collision) const {
std::size_t h1 = std::hash<Tile>()(collision.tile);
std::size_t h2 = std::hash<std::int32_t>()(static_cast<std::int32_t>(collision.flag));
return h1 ^ (h2 << 1);
}
};
}
std::unordered_map<region_key, std::unordered_set<collision>> cached_regions;
void cache_current_region() {
const auto get_tile_region = [](const Tile& tile) {
return ((std::int32_t)(((tile.X >> 6) << 8) | (tile.Y >> 6)));
};
const auto to_world_tile = [](const Pathfinding::TileNode& node, std::int32_t client_x, std::int32_t client_y, std::int32_t client_plane) {
return Tile(
node.X + client_x,
node.Y + client_y,
client_plane
);
};
Pathfinding::GenerateNodes();
const std::int32_t client_x = Internal::GetClientX();
const std::int32_t client_y = Internal::GetClientY();
const std::int32_t client_plane = Internal::GetClientPlane();
for (const auto& nodes : Pathfinding::GetNodes()) {
for (const auto& node : nodes) {
const auto flag = node.Flag;
const auto world_tile = to_world_tile(node, client_x, client_y, client_plane);
if ((flag & Pathfinding::UNINITIALIZED) || flag == Pathfinding::UNINITIALIZED) {
continue;
}
if ((flag == Pathfinding::CLOSED)) {
continue;
}
std::int32_t current_region = get_tile_region(world_tile);
cached_regions[region_key(current_region, client_plane)].emplace(world_tile, static_cast<Pathfinding::COLLISION_FLAG>(flag));
}
}
}
void paint() {
Paint::Clear();
std::vector<std::string> texts;
auto local_tile = Players::GetLocal().GetTile();
for (const auto& [key, collision_data] : cached_regions) {
texts.emplace_back(
"(" + std::to_string(key.region) + ", " + std::to_string(key.plane) + "): " + std::to_string(collision_data.size())
);
for (const auto& collision : collision_data) {
if (collision.tile.DistanceFrom(local_tile) > 12)
continue;
if ((collision.flag & Pathfinding::BLOCKED) || (collision.flag & Pathfinding::OCCUPIED)) {
Paint::DrawTile(collision.tile, 255, 0, 0, 255);
} else if (collision.flag & Pathfinding::NORTH) {
Paint::DrawTile(collision.tile, 255, 0, 255, 255);
} else if (collision.flag & Pathfinding::EAST) {
Paint::DrawTile(collision.tile, 0, 255, 0, 255);
} else if (collision.flag & Pathfinding::SOUTH) {
Paint::DrawTile(collision.tile, 0, 0, 255, 255);
} else if (collision.flag & Pathfinding::WEST) {
Paint::DrawTile(collision.tile, 255, 165, 0, 255);
}
}
}
Point p = Point(0, 5);
for (const auto& text : texts) {
p += Point(0, 20);
Paint::DrawString(text, p, 0, 255, 0, 255);
}
Paint::SwapBuffer();
}
void verify_and_clean() {
// verify each tile in a region actually belongs to it, if not, remove it
for (auto it = cached_regions.begin(); it != cached_regions.end();) {
const auto& [key, collision_data] = *it;
const auto get_tile_region = [](const Tile& tile) {
return ((std::int32_t)(((tile.X >> 6) << 8) | (tile.Y >> 6)));
};
for (const auto& collision : collision_data) {
if (get_tile_region(collision.tile) != key.region) {
it->second.erase(collision);
}
}
if (it->second.empty()) {
it = cached_regions.erase(it);
} else {
++it;
}
}
}
void write_collision_data(std::ofstream& file) {
file << "region,plane,x,y,flag\n";
verify_and_clean();
for (const auto& [key, collision_data] : cached_regions) {
for (const auto& collision : collision_data) {
if (collision.flag == Pathfinding::OPEN)
continue;
file << key.region << "," << key.plane << "," << collision.tile.X << "," << collision.tile.Y << "," << static_cast<std::int32_t>(collision.flag) << "\n";
}
}
}
void read_collision_data(std::ifstream& file) {
std::string line;
std::getline(file, line); // skip csv header
while (std::getline(file, line)) {
std::stringstream ss(line);
std::string token;
std::vector<std::string> tokens;
while (std::getline(ss, token, ',')) {
tokens.emplace_back(token);
}
const auto region = std::stoi(tokens[0]);
const auto plane = std::stoi(tokens[1]);
const auto x = std::stoi(tokens[2]);
const auto y = std::stoi(tokens[3]);
const auto flag = static_cast<Pathfinding::COLLISION_FLAG>(std::stoi(tokens[4]));
cached_regions[region_key(region, plane)].emplace(Tile(x, y, plane), flag);
}
}
std::string data_url() {
char *userprofile = std::getenv("USERPROFILE");
if (userprofile == nullptr)
return "";
return std::string(userprofile) + "\\AlpacaBot\\Collision Data\\collision_data.csv";
}
bool OnStart() {
SetLoopDelay(5);
std::string path = data_url();
std::ifstream file(path);
read_collision_data(file);
return true;
}
bool Loop() {
cache_current_region();
paint();
return true;
}
bool OnBreak() {
return true;
}
void OnEnd() {
char *userprofile = std::getenv("USERPROFILE");
if (userprofile == nullptr)
return;
std::string path = data_url();
if (!std::filesystem::exists(path)) {
std::filesystem::create_directories(std::string(userprofile) + "\\AlpacaBot\\Collision Data\\");
}
std::ofstream file(path);
write_collision_data(file);
}