Skip to content

Commit ae75b4d

Browse files
committed
Modding
1 parent 39ebb21 commit ae75b4d

File tree

4 files changed

+123
-22
lines changed

4 files changed

+123
-22
lines changed

Cargo.lock

Lines changed: 11 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ features = ["single_threaded"]
3131

3232
[dependencies.egui-hook]
3333
git = "https://github.com/jac3km4/egui-hook"
34-
rev = "v0.0.4"
34+
rev = "v0.0.5"
3535

3636
[dependencies.memhack]
3737
git = "https://github.com/jac3km4/memhack"

doc/MODS.md

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
## Modding
2+
3+
## example autoloot mod
4+
The mod below checks for a target to loot every 30 frames.
5+
To run it, save the code below in `ELEX2\system\plugins\crony\autoloot\main.rhai`.
6+
7+
```rhai
8+
fn initial_state() {
9+
#{ ticks: 0 }
10+
}
11+
12+
fn on_frame(state) {
13+
if state.ticks > 30 {
14+
loot_when_possible();
15+
16+
state.ticks = 0;
17+
}
18+
state.ticks += 1;
19+
}
20+
21+
fn loot_when_possible() {
22+
let looked_at = entity::get_look_at();
23+
if !entity::is_none(looked_at) {
24+
log("looting!");
25+
game::auto_loot(entity::get_player(), looked_at);
26+
}
27+
}
28+
```

src/host.rs

Lines changed: 83 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
use std::any::TypeId;
2+
use std::cell::RefCell;
23
use std::fmt::Debug;
4+
use std::path::PathBuf;
5+
use std::rc::Rc;
36
use std::sync::mpsc;
7+
use std::{fs, io};
48

59
use egui::{Align2, Color32, Context, FontId, Key, Rounding, Sense, TextEdit, Vec2, Window};
610
use flexi_logger::{Cleanup, Criterion, FileSpec, LogSpecification, Logger, Naming, WriteMode};
711
use heck::ToSnakeCase;
812
use rhai::plugin::CallableFunction;
9-
use rhai::{Dynamic, Engine, FnAccess, FnNamespace, Module, RegisterNativeFunction, Scope};
13+
use rhai::*;
1014

1115
use crate::{elex, handlers};
1216

@@ -18,6 +22,7 @@ pub struct ScriptHost {
1822
is_active: bool,
1923
engine: Engine,
2024
scope: Scope<'static>,
25+
mods: Vec<Mod>,
2126
}
2227

2328
impl Default for ScriptHost {
@@ -40,6 +45,7 @@ impl Default for ScriptHost {
4045
is_active: false,
4146
engine,
4247
scope: Scope::new(),
48+
mods: vec![],
4349
}
4450
}
4551
}
@@ -64,6 +70,14 @@ impl ScriptHost {
6470
}
6571
}
6672

73+
fn process_frame(&mut self) {
74+
for mod_ in &mut self.mods {
75+
let _res: Result<(), _> = self
76+
.engine
77+
.call_fn(&mut mod_.scope, &mod_.ast, "on_frame", vec![mod_.state.clone()]);
78+
}
79+
}
80+
6781
pub fn toggle(&mut self) {
6882
self.is_active = !self.is_active;
6983
}
@@ -103,11 +117,54 @@ impl ScriptHost {
103117
);
104118
module
105119
}
120+
121+
fn verify_version() -> bool {
122+
const SUPPORTED_VERSION_TS: u32 = 1647620648;
123+
let found_version = elex::check_version();
124+
125+
if found_version == SUPPORTED_VERSION_TS {
126+
log::info!("C.R.O.N.Y successfully initialized!");
127+
true
128+
} else {
129+
log::error!("Unsupported game version ({found_version}), exiting!");
130+
false
131+
}
132+
}
133+
134+
fn load_mods(&mut self) -> Result<(), io::Error> {
135+
let dir = std::env::current_exe()?
136+
.parent()
137+
.expect("no exe parent")
138+
.join("plugins")
139+
.join("crony");
140+
for entry in fs::read_dir(dir)? {
141+
let entry = entry?;
142+
if entry.file_type()?.is_dir() {
143+
let main = entry.path().join("main.rhai");
144+
if main.exists() {
145+
match self.init_mod(main.clone()) {
146+
Ok(()) => log::info!("Successfully loaded {}", main.display()),
147+
Err(err) => log::info!("Failed to initilize {}: {}", main.display(), err),
148+
}
149+
}
150+
}
151+
}
152+
153+
Ok(())
154+
}
155+
156+
fn init_mod(&mut self, path: PathBuf) -> Result<(), Box<EvalAltResult>> {
157+
let ast = self.engine.compile_file(path)?;
158+
let mut scope = Scope::new();
159+
let state = self.engine.call_fn(&mut scope, &ast, "initial_state", ())?;
160+
self.mods.push(Mod::new(ast, scope, state));
161+
Ok(())
162+
}
106163
}
107164

108165
impl egui_hook::App for ScriptHost {
109166
fn render(&mut self, ctx: &Context) {
110-
const DEFAULT_SIZE: Vec2 = Vec2::new(600., 320.);
167+
self.process_frame();
111168

112169
let was_active = self.is_active();
113170
if ctx.input().key_pressed(Key::Home) {
@@ -117,6 +174,8 @@ impl egui_hook::App for ScriptHost {
117174
return;
118175
}
119176

177+
const DEFAULT_SIZE: Vec2 = Vec2::new(600., 320.);
178+
120179
Window::new("CRONY GUI")
121180
.default_size(DEFAULT_SIZE)
122181
.show(ctx, |ui| {
@@ -165,15 +224,29 @@ impl egui_hook::App for ScriptHost {
165224
.start()
166225
.ok();
167226

168-
const SUPPORTED_VERSION_TS: u32 = 1647620648;
169-
let found_version = elex::check_version();
227+
Self::verify_version()
228+
}
170229

171-
if found_version == SUPPORTED_VERSION_TS {
172-
log::info!("C.R.O.N.Y successfully initialized!");
173-
true
174-
} else {
175-
log::error!("Unsupported game version ({found_version}), exiting!");
176-
false
230+
fn setup(&mut self, _ctx: &Context) {
231+
if let Err(err) = self.load_mods() {
232+
log::warn!("Failed to load mods: {err}")
233+
}
234+
}
235+
}
236+
237+
#[derive(Debug)]
238+
struct Mod {
239+
ast: AST,
240+
scope: Scope<'static>,
241+
state: Rc<RefCell<Dynamic>>,
242+
}
243+
244+
impl Mod {
245+
fn new(ast: AST, scope: Scope<'static>, state: Dynamic) -> Self {
246+
Self {
247+
ast,
248+
scope,
249+
state: Rc::new(RefCell::new(state)),
177250
}
178251
}
179252
}

0 commit comments

Comments
 (0)