Initial commit
commit
c2d4401845
@ -0,0 +1,4 @@
|
||||
/target
|
||||
**/*.rs.bk
|
||||
Cargo.lock
|
||||
/extern
|
@ -0,0 +1,5 @@
|
||||
[workspace]
|
||||
members = [
|
||||
"tes3mp-plugin",
|
||||
"tes3mp-test"
|
||||
]
|
@ -0,0 +1,12 @@
|
||||
# tes3mp-rs
|
||||
|
||||
Rust bindings for creating a TES3MP server plugin
|
||||
|
||||
Generating the bindings can be done via running
|
||||
|
||||
```
|
||||
./setup-externs
|
||||
./generate-rust.py
|
||||
```
|
||||
|
||||
this will generate a new `tes3mp-plugin/src/plugin/generated.rs` file
|
@ -0,0 +1,20 @@
|
||||
[General]
|
||||
# The default localAddress of 0.0.0.0 makes the server reachable at all of its local addresses
|
||||
localAddress = 0.0.0.0
|
||||
port = 25565
|
||||
maximumPlayers = 64
|
||||
hostname = My TES3MP server
|
||||
# 0 - Verbose (spam), 1 - Info, 2 - Warnings, 3 - Errors, 4 - Only fatal errors
|
||||
logLevel = 1
|
||||
password =
|
||||
|
||||
[Plugins]
|
||||
home = /server/data
|
||||
plugins = serverCore.lua,test.so
|
||||
|
||||
[MasterServer]
|
||||
enabled = false
|
||||
address = master.tes3mp.com
|
||||
port = 25561
|
||||
rate = 10000
|
||||
|
@ -0,0 +1 @@
|
||||
docker run -v "$PWD/config:/server/tes3mp-server-default.cfg" -v "$(realpath "$PWD/../target/debug/libtes3mp_test.so"):/server/data/scripts/test.so" -ti tes3mp/server
|
@ -0,0 +1,281 @@
|
||||
#!/usr/bin/env python3
|
||||
import subprocess
|
||||
import os
|
||||
from typing import List, Tuple, Optional
|
||||
|
||||
import regex
|
||||
|
||||
INCLUDES = [
|
||||
"extern/CrabNet/include/raknet",
|
||||
"extern/tes3mp/apps/openmw-mp",
|
||||
"extern/tes3mp"
|
||||
]
|
||||
|
||||
|
||||
def normalize_var(var: str):
|
||||
normalized = ''.join([char if char.islower() else ('_' + char.lower()) for char in var]).lstrip('_') \
|
||||
.replace('a_i', 'ai') \
|
||||
.replace('s_h_a_2_5_6', 'sha256') \
|
||||
.replace('i_p', 'ip')
|
||||
|
||||
if normalized == 'type':
|
||||
return '_type'
|
||||
|
||||
return normalized
|
||||
|
||||
|
||||
class CPPFunction:
|
||||
def __init__(self, name: str, args: List[Tuple[str, str]], return_type: str, comment: Optional[str]):
|
||||
self.name = name.strip()
|
||||
self.args = args
|
||||
self.return_type = return_type
|
||||
self.comment = comment
|
||||
|
||||
def __str__(self):
|
||||
args = ', '.join(map(str, self.args))
|
||||
return f'{self.comment}\n<CPPFunction "{self.name}" ({args}) -> {self.return_type}>'
|
||||
|
||||
|
||||
class CPPClass:
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
self.functions = dict()
|
||||
|
||||
def __str__(self):
|
||||
funcs = '\n\t'.join(map(str, self.functions)).lstrip()
|
||||
return f'<CPPClass "{self.name}" functions=[\n\t{funcs}\n]>'
|
||||
|
||||
|
||||
class PrimitiveHeaderParser:
|
||||
STATE_NONE = 0
|
||||
STATE_FUNC_LIST = 1
|
||||
|
||||
RE_CLASS = regex.compile(r'^\s*class\s+(\w+)')
|
||||
RE_START_COMMENT = regex.compile(r'\s*\/\*\*')
|
||||
RE_END_COMMENT = regex.compile(r'.+\*\/')
|
||||
RE_FUNCTION = regex.compile(r'^\s*static\s+((?:[a-zA-Z_\:\<\>\*\&0-9]+\s+[\*\&]?)+)([a-zA-Z0-9_]+)\(([^\)]+)?\)')
|
||||
RE_ARG = regex.compile(r'^\s*([a-zA-Z_\:\<\>\s\*\&0-9\.]+?)(?:(\s[\.\*\&]*)([a-zA-Z0-9_]+))?$')
|
||||
RE_FUNC_PAIR = regex.compile(r'{\s*"([A-Za-z0-9_\s]+)"\s*,\s*([A-Za-z0-9]+)\:\:([A-Za-z0-9]+)\s*}')
|
||||
|
||||
def __init__(self):
|
||||
self.classes = dict()
|
||||
self.current_class = None
|
||||
self.current_comment = None
|
||||
self.last_comment = None
|
||||
self.line = -1
|
||||
self.state = self.STATE_NONE
|
||||
self.functions = dict()
|
||||
self.current_line = ""
|
||||
|
||||
def parse_line(self, line: str):
|
||||
self.line += 1
|
||||
if line.rstrip().endswith(",") and self.current_comment is None:
|
||||
self.current_line += " " + line.rstrip()
|
||||
return
|
||||
|
||||
line = self.current_line + line
|
||||
self.current_line = ""
|
||||
|
||||
last_comment = self.last_comment
|
||||
self.last_comment = None
|
||||
|
||||
if self.state == self.STATE_FUNC_LIST:
|
||||
self.parse_function_list(line)
|
||||
return
|
||||
|
||||
new_class = self.RE_CLASS.match(line)
|
||||
if new_class is not None:
|
||||
self.current_class = CPPClass(new_class[1])
|
||||
self.classes[self.current_class.name] = self.current_class
|
||||
|
||||
if self.current_comment is None:
|
||||
new_comment = self.RE_START_COMMENT.match(line)
|
||||
if new_comment is not None:
|
||||
self.current_comment = new_comment[0]
|
||||
else:
|
||||
self.current_comment += "\n" + line
|
||||
if self.RE_END_COMMENT.match(line) is not None:
|
||||
self.last_comment = self.current_comment
|
||||
self.current_comment = None
|
||||
|
||||
return
|
||||
|
||||
new_function = self.RE_FUNCTION.match(line)
|
||||
if new_function is not None:
|
||||
failed_func = False
|
||||
args = []
|
||||
if new_function[3] is not None:
|
||||
for arg in new_function[3].split(','):
|
||||
arg = arg.split('=')[0].strip()
|
||||
|
||||
if arg in ['void', '...']:
|
||||
args.append(('', arg))
|
||||
continue
|
||||
|
||||
argr = self.RE_ARG.match(arg)
|
||||
if argr is None:
|
||||
print(f"Can't parse: {arg}")
|
||||
failed_func = True
|
||||
break
|
||||
else:
|
||||
name = "" if argr[3] is None else argr[3]
|
||||
type = "" if argr[1] is None else argr[1]
|
||||
pointer = "" if argr[2] is None else argr[2]
|
||||
|
||||
args.append((name.strip(), type.strip() + pointer.strip()))
|
||||
|
||||
if not failed_func:
|
||||
self.current_class.functions[new_function[2].strip()] = CPPFunction(new_function[2].strip(), args,
|
||||
new_function[1].strip(),
|
||||
last_comment)
|
||||
|
||||
if line.lstrip().startswith('static constexpr ScriptFunctionData functions'):
|
||||
self.state = self.STATE_FUNC_LIST
|
||||
|
||||
def parse_function_list(self, line):
|
||||
if line.strip().startswith('};'):
|
||||
self.state = self.STATE_NONE
|
||||
return
|
||||
|
||||
for item in regex.findall(self.RE_FUNC_PAIR, line):
|
||||
self.functions[item[0]] = (item[1], item[2])
|
||||
|
||||
|
||||
TYPE_TRANSLATION = {
|
||||
'const char*': '*const i8',
|
||||
'const char *': '*const i8',
|
||||
'bool': 'bool',
|
||||
'unsigned short': 'u16',
|
||||
'ScriptFunc': 'fn()',
|
||||
'int': 'i16',
|
||||
'unsigned int': 'u16',
|
||||
'unsigned char': 'u8',
|
||||
'char': 'i8',
|
||||
'float': 'f32',
|
||||
'double': 'f64',
|
||||
# No good way with these 2 types yet
|
||||
'va_list': '<REMOVE>',
|
||||
'boost::any': '<REMOVE>'
|
||||
}
|
||||
|
||||
PREFIX = 'rust'
|
||||
|
||||
RE_COMMENT_PREFIX = regex.compile(r'^([\\/\*]*)(.*)')
|
||||
RE_PARAM_PREFIX = regex.compile(r'\\param ([a-zA-Z_-]+)')
|
||||
RE_BRIEF = regex.compile(r'\\brief\s+')
|
||||
RE_RETURN = regex.compile(r'\s+[\\@]returns? ([a-zA-Z])')
|
||||
|
||||
|
||||
def main():
|
||||
os.chdir(os.path.dirname(__file__))
|
||||
gcc_command = list(["gcc", "-C", "-E"])
|
||||
|
||||
for include in INCLUDES:
|
||||
gcc_command.append('-I')
|
||||
gcc_command.append(include)
|
||||
|
||||
gcc_command.append("extern/tes3mp/apps/openmw-mp/Script/ScriptFunctions.hpp")
|
||||
|
||||
parser = PrimitiveHeaderParser()
|
||||
|
||||
with subprocess.Popen(gcc_command, stdout=subprocess.PIPE) as p:
|
||||
out, err = p.communicate()
|
||||
for line in out.splitlines():
|
||||
parser.parse_line(line.decode())
|
||||
|
||||
raw = f"#[no_mangle]\npub static mut prefix: [u8; {len(PREFIX)}] = *b\"{PREFIX}\";\n"
|
||||
|
||||
fancy = ""
|
||||
|
||||
for func_name in parser.functions:
|
||||
ref = parser.functions[func_name]
|
||||
func = parser.classes[ref[0]].functions[ref[1]]
|
||||
|
||||
args = ', '.join([TYPE_TRANSLATION[arg[1]] for arg in func.args])
|
||||
ret = ""
|
||||
|
||||
if func.return_type in ['const char*', 'const char *']:
|
||||
ret = " -> *const i8"
|
||||
elif func.return_type != "void":
|
||||
ret = f" -> {TYPE_TRANSLATION[func.return_type]}"
|
||||
|
||||
place_holder = "|" + ', '.join(['_'] * len(func.args)) + f'| {{ unreachable!("{func_name} was called before ' \
|
||||
f'set by TES3MP"); }};'
|
||||
|
||||
func_def = f"#[no_mangle]\npub static mut {PREFIX}{func_name}: fn({args}){ret} = {place_holder}"
|
||||
|
||||
if '<REMOVE>' in func_def:
|
||||
continue
|
||||
|
||||
raw += func_def + "\n"
|
||||
|
||||
fancy_name = normalize_var(func_name)
|
||||
fancy_args = []
|
||||
|
||||
for arg in func.args:
|
||||
fancy_arg = normalize_var(arg[0])
|
||||
|
||||
fancy_arg = fancy_arg.replace('__', '_')
|
||||
|
||||
if arg[1] in ['const char*', 'const char *']:
|
||||
fancy_args.append((fancy_arg, '&str', f'CString::new({fancy_arg}).unwrap_or_default().as_ptr()'))
|
||||
else:
|
||||
fancy_args.append((fancy_arg, TYPE_TRANSLATION[arg[1]], fancy_arg))
|
||||
|
||||
ret = ""
|
||||
if func.return_type in ['const char*', 'const char *']:
|
||||
ret = " -> String"
|
||||
elif func.return_type != "void":
|
||||
ret = f" -> {TYPE_TRANSLATION[func.return_type]}"
|
||||
|
||||
func_args = ', '.join([f"{x[0]}: {x[1]}" for x in fancy_args])
|
||||
call_args = ', '.join([x[2] for x in fancy_args])
|
||||
|
||||
if func.comment is not None:
|
||||
comment = ""
|
||||
for line in func.comment.strip().splitlines():
|
||||
line = line.strip()
|
||||
m = regex.match(RE_COMMENT_PREFIX, line)
|
||||
comment += ("///" + (' ' * len(m[1])) + m[2]).rstrip() + "\n"
|
||||
|
||||
comment = regex.sub(RE_BRIEF, '', comment)
|
||||
|
||||
def replace_param(m):
|
||||
return f"[`{normalize_var(m[1])}`]"
|
||||
|
||||
def replace_return(m):
|
||||
return f"\n/// Returns {m[1].lower()}"
|
||||
|
||||
comment = regex.sub(RE_PARAM_PREFIX, replace_param, comment)
|
||||
comment = regex.sub(RE_RETURN, replace_return, comment)
|
||||
comment = comment.replace('"[Script]:"', '`[Script]:`')
|
||||
|
||||
fancy += comment
|
||||
|
||||
fancy += f"pub fn {fancy_name}({func_args}){ret} {{\n"
|
||||
fancy += " unsafe {\n"
|
||||
|
||||
if func.return_type in ['const char*', 'const char *']:
|
||||
fancy += f" CStr::from_ptr(raw::{PREFIX}{func_name}({call_args}))\n"
|
||||
fancy += f" .to_str()\n"
|
||||
fancy += f" .unwrap_or_default()\n"
|
||||
fancy += f" .to_string()\n"
|
||||
else:
|
||||
fancy += f" raw::{PREFIX}{func_name}({call_args})\n"
|
||||
|
||||
fancy += " }\n"
|
||||
fancy += "}\n\n"
|
||||
|
||||
whole = "use std::ffi::{CStr, CString};\n\n"
|
||||
whole += "#[allow(non_upper_case_globals)]\npub mod raw {\n"
|
||||
for line in raw.splitlines():
|
||||
whole += " " + line + "\n"
|
||||
whole += "}\n\n"
|
||||
whole += fancy
|
||||
|
||||
with open("tes3mp-plugin/src/plugin/generated.rs", "w") as f:
|
||||
f.write(whole)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
@ -0,0 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e;
|
||||
|
||||
cd "$(dirname "$(realpath "$0")")";
|
||||
|
||||
test -d extern || mkdir extern;
|
||||
test -d extern/tes3mp || git clone --branch 0.7.1 https://github.com/TES3MP/openmw-tes3mp.git extern/tes3mp
|
||||
test -d extern/CrabNet || git clone https://github.com/TES3MP/CrabNet extern/CrabNet
|
@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "tes3mp-plugin"
|
||||
version = "0.1.0"
|
||||
authors = ["eater <=@eater.me>"]
|
||||
edition = "2018"
|
||||
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
@ -0,0 +1,17 @@
|
||||
# tes3mp-plugin
|
||||
|
||||
A crate for helping you make tes3mp plugins in Rust
|
||||
|
||||
# How to use
|
||||
|
||||
Because Rust won't allow you to re-export symbols from other crates, this crate can't be used like normal crates, the advised manner of using this crate is the following:
|
||||
|
||||
```bash
|
||||
cd crate-dir
|
||||
git status || git init
|
||||
mkdir extern
|
||||
git submodule add https://git.cijber.net/teamnwah/tes3mp-plugin extern/tes3mp-plugin
|
||||
ln -s extern/tes3mp-plugin/tes3mp-plugin/src/plugin src/plugin
|
||||
```
|
||||
|
||||
This will make the `plugin` like it's part of your crate, which allows us to export the C symbols
|
@ -0,0 +1,3 @@
|
||||
pub mod plugin;
|
||||
|
||||
pub use plugin::*;
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,516 @@
|
||||
#![allow(unused)]
|
||||
|
||||
pub mod generated;
|
||||
|
||||
pub use generated::*;
|
||||
|
||||
pub const LOG_VERBOSE: u16 = 0;
|
||||
pub const LOG_INFO: u16 = 1;
|
||||
pub const LOG_WARN: u16 = 2;
|
||||
pub const LOG_ERROR: u16 = 3;
|
||||
pub const LOG_FATAL: u16 = 4;
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! instance {
|
||||
($call:tt, $($argument:expr),+) => {
|
||||
let instance = unsafe {
|
||||
EVENTS_INSTANCE
|
||||
.as_ref()
|
||||
.expect(format!("No events instance created: {}\n", stringify!($call)).as_str())
|
||||
};
|
||||
instance.$call($($argument),+);
|
||||
};
|
||||
|
||||
($call:tt) => {
|
||||
let instance = unsafe {
|
||||
EVENTS_INSTANCE
|
||||
.as_ref()
|
||||
.expect(format!("No events instance created: {}\n", stringify!($call)).as_str())
|
||||
};
|
||||
instance.$call();
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! use_events {
|
||||
($events:ident) => {
|
||||
use std::ffi::CStr;
|
||||
|
||||
static mut EVENTS_INSTANCE: Option<$events> = None;
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnServerInit() {
|
||||
unsafe {
|
||||
if (EVENTS_INSTANCE.is_none()) {
|
||||
EVENTS_INSTANCE = Some($events::new());
|
||||
}
|
||||
}
|
||||
|
||||
instance!(on_server_init);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnServerPostInit() {
|
||||
instance!(on_server_post_init);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnServerExit(is_error: bool) {
|
||||
instance!(on_server_exit, is_error);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnActorAI(player_id: u16, description: *const i8) {
|
||||
instance!(on_actor_ai, player_id, unsafe {
|
||||
CStr::from_ptr(description).to_str().unwrap_or_default()
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnActorCellChange(player_id: u16, description: *const i8) {
|
||||
instance!(on_actor_cell_change, player_id, unsafe {
|
||||
CStr::from_ptr(description).to_str().unwrap_or_default()
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnActorDeath(player_id: u16, description: *const i8) {
|
||||
instance!(on_actor_death, player_id, unsafe {
|
||||
CStr::from_ptr(description).to_str().unwrap_or_default()
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnActorEquipment(player_id: u16, description: *const i8) {
|
||||
instance!(on_actor_equipment, player_id, unsafe {
|
||||
CStr::from_ptr(description).to_str().unwrap_or_default()
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnActorList(player_id: u16, description: *const i8) {
|
||||
instance!(on_actor_list, player_id, unsafe {
|
||||
CStr::from_ptr(description).to_str().unwrap_or_default()
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnActorTest(player_id: u16, description: *const i8) {
|
||||
instance!(on_actor_test, player_id, unsafe {
|
||||
CStr::from_ptr(description).to_str().unwrap_or_default()
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnCellDeletion(description: *const i8) {
|
||||
instance!(on_cell_deletion, unsafe {
|
||||
CStr::from_ptr(description).to_str().unwrap_or_default()
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnCellLoad(description: *const i8) {
|
||||
instance!(on_cell_load, unsafe {
|
||||
CStr::from_ptr(description).to_str().unwrap_or_default()
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnCellUnload(description: *const i8) {
|
||||
instance!(on_cell_unload, unsafe {
|
||||
CStr::from_ptr(description).to_str().unwrap_or_default()
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnContainer(player_id: u16, description: *const i8) {
|
||||
instance!(on_container, player_id, unsafe {
|
||||
CStr::from_ptr(description).to_str().unwrap_or_default()
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnDoorState(player_id: u16, description: *const i8) {
|
||||
instance!(on_door_state, player_id, unsafe {
|
||||
CStr::from_ptr(description).to_str().unwrap_or_default()
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnGUIAction(player_id: u16, message_box_id: i16, data: *const i8) {
|
||||
instance!(on_gui_action, player_id, message_box_id, unsafe {
|
||||
CStr::from_ptr(data).to_str().unwrap_or_default()
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnMpNumIncrement(current_mp_num: i16) {
|
||||
instance!(on_mp_num_increment, current_mp_num);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnObjectActivate(player_id: u16, description: *const i8) {
|
||||
instance!(on_object_activate, player_id, unsafe {
|
||||
CStr::from_ptr(description).to_str().unwrap_or_default()
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnObjectDelete(player_id: u16, description: *const i8) {
|
||||
instance!(on_object_delete, player_id, unsafe {
|
||||
CStr::from_ptr(description).to_str().unwrap_or_default()
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnObjectLock(player_id: u16, description: *const i8) {
|
||||
instance!(on_object_lock, player_id, unsafe {
|
||||
CStr::from_ptr(description).to_str().unwrap_or_default()
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnObjectPlace(player_id: u16, description: *const i8) {
|
||||
instance!(on_object_place, player_id, unsafe {
|
||||
CStr::from_ptr(description).to_str().unwrap_or_default()
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnObjectScale(player_id: u16, description: *const i8) {
|
||||
instance!(on_object_scale, player_id, unsafe {
|
||||
CStr::from_ptr(description).to_str().unwrap_or_default()
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnObjectSpawn(player_id: u16, description: *const i8) {
|
||||
instance!(on_object_spawn, player_id, unsafe {
|
||||
CStr::from_ptr(description).to_str().unwrap_or_default()
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnObjectState(player_id: u16, description: *const i8) {
|
||||
instance!(on_object_state, player_id, unsafe {
|
||||
CStr::from_ptr(description).to_str().unwrap_or_default()
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnObjectTrap(player_id: u16, description: *const i8) {
|
||||
instance!(on_object_trap, player_id, unsafe {
|
||||
CStr::from_ptr(description).to_str().unwrap_or_default()
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnPlayerAttribute(player_id: u16) {
|
||||
instance!(on_player_attribute, player_id);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnPlayerBook(player_id: u16) {
|
||||
instance!(on_player_book, player_id);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnPlayerBounty(player_id: u16) {
|
||||
instance!(on_player_bounty, player_id);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnPlayerCellChange(player_id: u16) {
|
||||
instance!(on_player_cell_change, player_id);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnPlayerConnect(player_id: u16) {
|
||||
instance!(on_player_connect, player_id);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnPlayerDeath(player_id: u16) {
|
||||
instance!(on_player_death, player_id);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnPlayerDisconnect(player_id: u16) {
|
||||
instance!(on_player_disconnect, player_id);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnPlayerDisposition(player_id: u16) {
|
||||
instance!(on_player_disposition, player_id);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnPlayerEndCharGen(player_id: u16) {
|
||||
instance!(on_player_end_char_gen, player_id);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnPlayerEquipment(player_id: u16) {
|
||||
instance!(on_player_equipment, player_id);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnPlayerFaction(player_id: u16) {
|
||||
instance!(on_player_faction, player_id);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnPlayerInput(player_id: u16) {
|
||||
instance!(on_player_input, player_id);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnPlayerInventory(player_id: u16) {
|
||||
instance!(on_player_inventory, player_id);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnPlayerItemUse(player_id: u16) {
|
||||
instance!(on_player_item_use, player_id);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnPlayerJournal(player_id: u16) {
|
||||
instance!(on_player_journal, player_id);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnPlayerLevel(player_id: u16) {
|
||||
instance!(on_player_level, player_id);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnPlayerMiscellaneous(player_id: u16) {
|
||||
instance!(on_player_miscellaneous, player_id);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnPlayerQuickKeys(player_id: u16) {
|
||||
instance!(on_player_quick_keys, player_id);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnPlayerReputation(player_id: u16) {
|
||||
instance!(on_player_reputation, player_id);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnPlayerRest(player_id: u16) {
|
||||
instance!(on_player_rest, player_id);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnPlayerResurrect(player_id: u16) {
|
||||
instance!(on_player_resurrect, player_id);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnPlayerSendMessage(player_id: u16, message: *const i8) {
|
||||
instance!(on_player_send_message, player_id, unsafe {
|
||||
CStr::from_ptr(message).to_str().unwrap_or_default()
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnPlayerShapeshift(player_id: u16) {
|
||||
instance!(on_player_shapeshift, player_id);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnPlayerSkill(player_id: u16) {
|
||||
instance!(on_player_skill, player_id);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnPlayerSpellbook(player_id: u16) {
|
||||
instance!(on_player_spellbook, player_id);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnPlayerTopic(player_id: u16) {
|
||||
instance!(on_player_topic, player_id);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnRecordDynamic(player_id: u16) {
|
||||
instance!(on_record_dynamic, player_id);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnRequestDataFileList() {
|
||||
instance!(on_request_data_file_list);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnScriptGlobalShort(player_id: u16) {
|
||||
instance!(on_script_global_short, player_id);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnServerScriptCrash(error: *const i8) {
|
||||
instance!(on_server_script_crash, unsafe {
|
||||
CStr::from_ptr(error).to_str().unwrap_or_default()
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnVideoPlay(player_id: u16, description: *const i8) {
|
||||
instance!(on_video_play, player_id, unsafe {
|
||||
CStr::from_ptr(description).to_str().unwrap_or_default()
|
||||
});
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnWorldKillCount(player_id: u16) {
|
||||
instance!(on_world_kill_count, player_id);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnWorldMap(player_id: u16) {
|
||||
instance!(on_world_map, player_id);
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
#[allow(non_snake_case)]
|
||||
pub fn OnWorldWeather(player_id: u16) {
|
||||
instance!(on_world_weather, player_id);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub trait Events: Sized {
|
||||
fn new() -> Self;
|
||||
|
||||
fn on_actor_ai(&self, player_id: u16, description: &str) {}
|
||||
fn on_actor_cell_change(&self, player_id: u16, description: &str) {}
|
||||
fn on_actor_death(&self, player_id: u16, description: &str) {}
|
||||
fn on_actor_equipment(&self, player_id: u16, description: &str) {}
|
||||
fn on_actor_list(&self, player_id: u16, description: &str) {}
|
||||
fn on_actor_test(&self, player_id: u16, description: &str) {}
|
||||
|
||||
fn on_cell_deletion(&self, description: &str) {}
|
||||
fn on_cell_load(&self, description: &str) {}
|
||||
fn on_cell_unload(&self, description: &str) {}
|
||||
|
||||
fn on_container(&self, player_id: u16, description: &str) {}
|
||||
fn on_door_state(&self, player_id: u16, description: &str) {}
|
||||
|
||||
fn on_gui_action(&self, player_id: u16, message_box_id: i16, data: &str) {}
|
||||
|
||||
fn on_mp_num_increment(&self, current_mp_num: i16) {}
|
||||
|
||||
fn on_object_activate(&self, player_id: u16, description: &str) {}
|
||||
fn on_object_delete(&self, player_id: u16, description: &str) {}
|
||||
fn on_object_lock(&self, player_id: u16, description: &str) {}
|
||||
fn on_object_place(&self, player_id: u16, description: &str) {}
|
||||
fn on_object_scale(&self, player_id: u16, description: &str) {}
|
||||
fn on_object_spawn(&self, player_id: u16, description: &str) {}
|
||||
fn on_object_state(&self, player_id: u16, description: &str) {}
|
||||
fn on_object_trap(&self, player_id: u16, description: &str) {}
|
||||
|
||||
fn on_player_attribute(&self, player_id: u16) {}
|
||||
fn on_player_book(&self, player_id: u16) {}
|
||||
fn on_player_bounty(&self, player_id: u16) {}
|
||||
fn on_player_cell_change(&self, player_id: u16) {}
|
||||
fn on_player_connect(&self, player_id: u16) {}
|
||||
fn on_player_death(&self, player_id: u16) {}
|
||||
fn on_player_disconnect(&self, player_id: u16) {}
|
||||
fn on_player_disposition(&self, player_id: u16) {}
|
||||
fn on_player_end_char_gen(&self, player_id: u16) {}
|
||||
fn on_player_equipment(&self, player_id: u16) {}
|
||||
fn on_player_faction(&self, player_id: u16) {}
|
||||
fn on_player_input(&self, player_id: u16) {}
|
||||
fn on_player_inventory(&self, player_id: u16) {}
|
||||
fn on_player_item_use(&self, player_id: u16) {}
|
||||
fn on_player_journal(&self, player_id: u16) {}
|
||||
fn on_player_level(&self, player_id: u16) {}
|
||||
fn on_player_miscellaneous(&self, player_id: u16) {}
|
||||
fn on_player_quick_keys(&self, player_id: u16) {}
|
||||
fn on_player_reputation(&self, player_id: u16) {}
|
||||
fn on_player_rest(&self, player_id: u16) {}
|
||||
fn on_player_resurrect(&self, player_id: u16) {}
|
||||
fn on_player_send_message(&self, player_id: u16, message: &str) {}
|
||||
fn on_player_shapeshift(&self, player_id: u16) {}
|
||||
fn on_player_skill(&self, player_id: u16) {}
|
||||
fn on_player_spellbook(&self, player_id: u16) {}
|
||||
fn on_player_topic(&self, player_id: u16) {}
|
||||
|
||||
fn on_record_dynamic(&self, player_id: u16) {}
|
||||
|
||||
fn on_request_data_file_list(&self) {}
|
||||
|
||||
fn on_script_global_short(&self, player_id: u16) {}
|
||||
|
||||
fn on_server_exit(&self, is_error: bool) {}
|
||||
fn on_server_init(&self) {}
|
||||
fn on_server_post_init(&self) {}
|
||||
fn on_server_script_crash(&self, error: &str) {}
|
||||
|
||||
fn on_video_play(&self, player_id: u16, description: &str) {}
|
||||
|
||||
fn on_world_kill_count(&self, player_id: u16) {}
|
||||
fn on_world_map(&self, player_id: u16) {}
|
||||
fn on_world_weather(&self, player_id: u16) {}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
[package]
|
||||
name = "tes3mp-test"
|
||||
version = "0.1.0"
|
||||
authors = ["eater <=@eater.me>"]
|
||||
edition = "2018"
|
||||
|
||||
[lib]
|
||||
crate-type = ["staticlib", "cdylib"]
|
||||
|
||||
[profile.dev]
|
||||
panic = "abort"
|
@ -0,0 +1,21 @@
|
||||
use crate::plugin::Events;
|
||||
|
||||
mod plugin;
|
||||
|
||||
struct Server;
|
||||
|
||||
impl Events for Server {
|
||||
fn new() -> Self {
|
||||
Server
|
||||
}
|
||||
|
||||
fn on_server_init(&self) {
|
||||
plugin::log_message(plugin::LOG_WARN, "Hello from Rust :3");
|
||||
}
|
||||
|
||||
fn on_server_post_init(&self) {
|
||||
plugin::log_message(plugin::LOG_FATAL, "Hi!?");
|
||||
}
|
||||
}
|
||||
|
||||
use_events!(Server);
|
@ -0,0 +1 @@
|
||||
../../tes3mp-plugin/src/plugin
|
Loading…
Reference in New Issue