Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
12 changes: 12 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,15 @@ scripts.pak
.pack_dev_cere.bat
output.txt
output.log
/as_linter/build
/msc_transpiler/__pycache__
/msc_transpiler/commands/__pycache__
/msc_transpiler/tests/__pycache__
/msc_transpiler/pipeline/__pycache__
/msc_transpiler/output/__pycache__
/msc_transpiler/expressions/__pycache__
/angelscript_output
lint_report.json
lint_report.txt
transpiler_errors.log
/_tmp_as_fixcheck
66 changes: 66 additions & 0 deletions as_linter/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
cmake_minimum_required(VERSION 3.16)
project(as_linter LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Static MSVC runtime for standalone distribution
if(MSVC)
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
add_compile_options(/W3 /EHsc)
add_compile_definitions(_CRT_SECURE_NO_WARNINGS)
endif()

# Paths to AngelScript SDK and addons in the game repo
set(AS_ROOT "${CMAKE_SOURCE_DIR}/../../../Documents/GitHub/MSC/MasterSwordRebirth/thirdparty/angelscript")
set(ADDON_ROOT "${CMAKE_SOURCE_DIR}/../../../Documents/GitHub/MSC/MasterSwordRebirth/src/game/shared/ms/angelscript/addons")

# Allow overriding from command line
if(DEFINED ANGELSCRIPT_ROOT)
set(AS_ROOT "${ANGELSCRIPT_ROOT}")
endif()
if(DEFINED ADDON_DIR)
set(ADDON_ROOT "${ADDON_DIR}")
endif()

message(STATUS "AngelScript SDK: ${AS_ROOT}")
message(STATUS "Addons dir: ${ADDON_ROOT}")

# Collect AngelScript SDK source files
file(GLOB AS_SDK_SOURCES "${AS_ROOT}/*.cpp")

# Addon source files
set(ADDON_SOURCES
"${ADDON_ROOT}/scriptstdstring/scriptstdstring.cpp"
"${ADDON_ROOT}/scriptstdstring/scriptstdstring_utils.cpp"
"${ADDON_ROOT}/scriptarray/scriptarray.cpp"
"${ADDON_ROOT}/scriptdictionary/scriptdictionary.cpp"
"${ADDON_ROOT}/scriptbuilder/scriptbuilder.cpp"
"${ADDON_ROOT}/scriptmath/scriptmath.cpp"
)

# Our linter source files
set(LINTER_SOURCES
src/main.cpp
src/linter_engine.cpp
src/linter_registration.cpp
src/linter_report.cpp
src/linter_include_handler.cpp
)

add_executable(as_linter
${LINTER_SOURCES}
${AS_SDK_SOURCES}
${ADDON_SOURCES}
)

target_include_directories(as_linter PRIVATE
"${AS_ROOT}/include"
"${ADDON_ROOT}"
"${CMAKE_SOURCE_DIR}/src"
)

# On Windows x86, AngelScript needs the asm caller
if(MSVC AND CMAKE_SIZEOF_VOID_P EQUAL 4)
target_compile_definitions(as_linter PRIVATE AS_MAX_PORTABILITY)
endif()
211 changes: 211 additions & 0 deletions as_linter/src/linter_base_script.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
#pragma once

// CGameScript base class defined as AngelScript source code.
// This is compiled into every module so transpiled scripts can inherit from it.
// It mirrors the CGameScript type registered in ASScriptClasses.cpp but as a
// script-defined class instead of a native type.
//
// Every method that transpiled scripts call on 'this' (implicitly or
// explicitly) must appear here so the AS compiler can resolve the symbol.

static const char* g_BaseScript = R"AS(

// Base class that all transpiled scripts inherit from
class CGameScript
{
// Variable storage (simulated — in real game this uses C++ std::map)
dictionary@ m_vars;

// Entity handle fields used by transpiled scripts
CBaseEntity@ m_hTarget;
CBaseEntity@ m_hLastCreated;
CBaseEntity@ m_hLastUsed;
CBaseEntity@ m_hLastStruck;
CBaseEntity@ m_hLastStruckByMe;
CBaseEntity@ m_hLastSeen;
CBaseEntity@ m_hAttackTarget;

CGameScript()
{
@m_vars = dictionary();
@m_hTarget = null;
@m_hLastCreated = null;
@m_hLastUsed = null;
@m_hLastStruck = null;
@m_hLastStruckByMe = null;
@m_hLastSeen = null;
@m_hAttackTarget = null;
}

// ── Variable storage ──────────────────────────────────────────
void SetVar(const string &in name, const string &in value)
{
m_vars.set(name, value);
}

void SetVar(const string &in name, float value)
{
m_vars.set(name, formatFloat(value, "", 0, 6));
}

void SetVar(const string &in name, int value)
{
m_vars.set(name, "" + value);
}

string GetVar(const string &in name, const string &in defaultValue = "")
{
string result;
if (m_vars.get(name, result))
return result;
return defaultValue;
}

float GetVarFloat(const string &in name, float defaultValue = 0.0f)
{
string result;
if (m_vars.get(name, result))
{
uint dummy;
return float(parseFloat(result, dummy));
}
return defaultValue;
}

int GetVarInt(const string &in name, int defaultValue = 0)
{
string result;
if (m_vars.get(name, result))
{
uint dummy;
return int(parseInt(result, 10, dummy));
}
return defaultValue;
}

bool HasVar(const string &in name)
{
return m_vars.exists(name);
}

void RemoveVar(const string &in name)
{
m_vars.delete(name);
}

void ClearVars()
{
m_vars.deleteAll();
}

// ── Entity ownership ──────────────────────────────────────────
CBaseEntity@ GetOwner() { return null; }
void SetOwner(CBaseEntity@ pEntity) {}
bool IsValidOwner() { return false; }

// ── Properties (called on self by transpiled scripts) ─────────
void SetHealth(float hp) {}
void SetHealth(int hp) {}
void SetMaxHealth(float hp) {}
void SetName(const string &in name) {}
void SetModel(const string &in model) {}
void SetInvincible(bool val) {}
void SetRace(const string &in race) {}
void SetWidth(float w) {}
void SetHeight(float h) {}
void SetWidth(int w) {}
void SetHeight(int h) {}
void SetRoam(bool val) {}
void SetRoam(int val) {}
void SetModelBody(int group, int body) {}
void SetSolid(int val) {}
void SetSolid(const string &in val) {}
void SetFly(bool val) {}
void SetFly(int val) {}
void SetNoPush(bool val) {}
void SetNoPush(int val) {}
void SetGravity(float val) {}
void SetGravity(int val) {}
void SetMoveAnim(const string &in anim) {}
void SetIdleAnim(const string &in anim) {}
void SetMoveDest(const string &in dest) {}
void SetMoveDest(const Vector3 &in dest) {}
void SetAngles(const string &in angles) {}
void SetAngles(const Vector3 &in angles) {}
void SetCallback(const string &in a) {}
void SetCallback(const string &in a, const string &in b) {}
void SetCallback(const string &in a, const string &in b, const string &in c) {}
void SetRepeatDelay(float delay) {}
void SetRepeatDelay(int delay) {}
void SetStat(const string &in stat, int val) {}
void SetStat(const string &in stat, const string &in val) {}
void SetMenuAutoOpen(bool val) {}
void SetMenuAutoOpen(int val) {}
void SetFOV(float fov) {}
void SetFOV(int fov) {}
void SetBloodType(const string &in type) {}
void SetSpeed(float speed) {}
void SetSpeed(int speed) {}
void SetBBox(const string &in mins, const string &in maxs) {}
void SetBBox(const Vector3 &in mins, const Vector3 &in maxs) {}
void SetScriptFlags(const string &in flags) {}
void SetScriptFlags(const string &in a, const string &in b) {}
void SetTarget(const string &in target) {}
void SetTarget(CBaseEntity@ target) {}
void SetDamageType(const string &in type) {}
void SetDamageType(const string &in type, const string &in extra) {}

// ── Animation ────────────────────────────────────────────────
void PlayAnim(const string &in anim) {}
void PlayAnim(const string &in anim, float framerate) {}

// ── Script lifecycle ─────────────────────────────────────────
void RemoveScript() {}
void RemoveScript(const string &in script) {}

// ── Virtual event callbacks ──────────────────────────────────
// These must be declared so that subclasses can use 'override'
void OnSpawn() {}
void OnThink() {}
void OnDamage(int amount) {}
void OnDeath(CBaseEntity@ attacker) {}
void OnTakeDamage(CBaseEntity@ inflictor, CBaseEntity@ attacker, int damage, int damageType) {}
void OnTouch(CBaseEntity@ other) {}
void OnUse(CBaseEntity@ activator, CBaseEntity@ caller, int useType) {}
void OnHitByAttack(CBaseEntity@ attacker, int damage) {}
void OnDeploy() {}
void OnPickup(CBaseEntity@ player) {}
void OnDrop() {}
void OnScriptSay(const string &in text) {}
void OnHeardSound(CBaseEntity@ source, Vector3 origin) {}
void OnDamagedOther(CBaseEntity@ victim, int damage) {}
void OnParry(CBaseEntity@ attacker) {}
void OnPostSpawn() {}

// NPC-specific callbacks
void OnAttackTarget(CBaseEntity@ target) {}
void OnHuntTarget(CBaseEntity@ target) {}
void OnFlee() {}
void OnAttackDoDamage(CBaseEntity@ target) {}
void OnStruck(CBaseEntity@ attacker, int damage) {}
void OnFlinch() {}
void OnTargetValidate(CBaseEntity@ target) {}
void OnAidingAlly(CBaseEntity@ ally, CBaseEntity@ enemy) {}
void OnSuspendAI() {}

// Timer callback
void OnRepeatTimer() {}
}

// Interface for script-defined classes
interface IScriptInterface
{
}

// Factory function
CGameScript@ CreateScript(const string &in className)
{
return CGameScript();
}

)AS";
51 changes: 51 additions & 0 deletions as_linter/src/linter_engine.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#include "linter_engine.h"
#include "linter_registration.h"
#include <cstdio>

// Thread-local message collection for the current file being compiled
static std::vector<LinterMessage> g_CurrentMessages;

std::vector<LinterMessage>& GetCurrentMessages() { return g_CurrentMessages; }
void ClearCurrentMessages() { g_CurrentMessages.clear(); }

// AngelScript message callback — collects messages into the vector
static void MessageCallback(const asSMessageInfo* msg, void* /*param*/)
{
LinterMessage m;
m.section = msg->section ? msg->section : "";
m.row = msg->row;
m.col = msg->col;
m.type = msg->type;
m.message = msg->message ? msg->message : "";
g_CurrentMessages.push_back(m);
}

asIScriptEngine* CreateLinterEngine()
{
asIScriptEngine* engine = asCreateScriptEngine();
if (!engine)
{
fprintf(stderr, "ERROR: Failed to create AngelScript engine.\n");
return nullptr;
}

// Set message callback
int r = engine->SetMessageCallback(asFUNCTION(MessageCallback), nullptr, asCALL_CDECL);
if (r < 0)
{
fprintf(stderr, "ERROR: Failed to set message callback.\n");
engine->ShutDownAndRelease();
return nullptr;
}

// Match engine properties from CAngelScriptManager::Initialize()
engine->SetEngineProperty(asEP_MAX_STACK_SIZE, 1024 * 1024); // 1 MB
engine->SetEngineProperty(asEP_ALLOW_UNSAFE_REFERENCES, true);
engine->SetEngineProperty(asEP_OPTIMIZE_BYTECODE, true);
engine->SetEngineProperty(asEP_COPY_SCRIPT_SECTIONS, true);

// Register all game bindings (types, methods, global functions, enums, etc.)
RegisterAllBindings(engine);

return engine;
}
21 changes: 21 additions & 0 deletions as_linter/src/linter_engine.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#pragma once

#include <angelscript.h>
#include <string>
#include <vector>

// Message info collected during compilation
struct LinterMessage {
std::string section;
int row;
int col;
int type; // asMSGTYPE_ERROR, asMSGTYPE_WARNING, asMSGTYPE_INFORMATION
std::string message;
};

// Initialize the AngelScript engine with all game bindings
asIScriptEngine* CreateLinterEngine();

// Get collected messages for the current file (cleared per-file)
std::vector<LinterMessage>& GetCurrentMessages();
void ClearCurrentMessages();
Loading