#SOLVED - DLL Mod made in Rust not running recurring task right?

157 messages · Page 1 of 1 (latest)

worn basin
#

Making a dll mod that adds a regen based on your vigor/mind/endurance stat level.
I AM VERY NEW TO RUST so I barely know what I'm doing.

Hoping someone who does know can take a look and try to get it to work.

I have isolated the problem to somewhere inside "cs_task.run_recurring". After commenting the insides out, Elden Ring doesn't CTD when I open it with the dll.

#

@rich moon Any chance you could take a look?

#

Other info:
Using fromsoftware-rs and rust-ini crates
Made for Elden Ring

worn basin
south flare
#

also, don't read ini every frame, it's kinda too much

#

you can read it once and move into closure

worn basin
#

Where would I put the line to read the ini to have it called once?

south flare
worn basin
#

Ok I have fixed the float/int issue i think and it no longer CTDs

#

Where exactly can the read_write_ini line go? I put it elsewhere and it gave errors like this:

#

That is with the line here:

#

As i said before I am VERY new to rust so I dont understand a lot of it

#

Just learning as i go

south flare
#

read ini before creating new task

#

and use move keyword on the task closure to access read config inside it

worn basin
#

So now that those things are fixed, I need to get the regen working... it seems like its not looping like I need

#

CTD problem from my DLL mod made in Rust - SOLVED

rich moon
#

But yeah ig every frame is too much

south flare
#

you don't need to read and deserialize ini every frame, only when file is actually changed

rich moon
#

Yeah i had no clue thats possible, ill take a look at it

worn basin
#

hello again @rich moon
any ideas for how to fix this mod?

rich moon
#

Do some println debugging and look if the code even reaches the stuff inside the if statement

worn basin
#

ok

rich moon
#

Your println will show up in the mod engine 3 console

worn basin
#

got it

worn basin
#

Not seeing any printed lines in the console...?

#

tried it out on the staticbarsize mod too

#

still not seeing anything

#

Also it is very late here so i need to stop for tonight and sleep so we can pick this up later?

rich moon
worn basin
#

DLL Mod made in Rust not running recurring task right?

unique iris
#

A.e:

#
const CAMERA_CONFIG_TOML: &str = "camera_config.toml";

#[derive(Deserialize)]
#[serde(default, deny_unknown_fields)]
struct CameraSettings {
    field_of_view: f32,
    distance_multiplier: f32,
    render_distance_start: f32,
    render_distance_end: f32,
    aspect_width: f32,
    aspect_height: f32,
}

impl Default for CameraSettings {
    fn default() -> Self {
        Self {
            field_of_view: 48.0,
            distance_multiplier: 1.0,
            render_distance_start: 0.05,
            render_distance_end: 10_000.0,
            aspect_width: 16.0,
            aspect_height: 9.0,
        }
    }
}

#[derive(Default)]
struct CameraConfig {
    field_of_view: f32,
    distance_multiplier: f32,
    render_distance_start: f32,
    render_distance_end: f32,
    aspect_ratio: f32,
    is_reloaded: bool,
}

static CAMERA_CONFIG: OnceLock<RwLock<CameraConfig>> = OnceLock::new();

fn get_camera_config() -> &'static RwLock<CameraConfig> {
    CAMERA_CONFIG.get_or_init(|| {
        let config = read_camera_config();
        RwLock::new(config)
    })
}

fn read_camera_config() -> CameraConfig {
    let mut camera_config = CameraConfig::default();
    if let Some(config) = std::env::current_exe().ok().and_then(|path_buf| {
        path_buf
            .parent()
            .map(|path_buf| path_buf.join(CAMERA_CONFIG_TOML))
            .and_then(|path_buf| std::fs::read_to_string(path_buf.as_path()).ok())
    }) {
        let camera_settings = toml::from_str::<CameraSettings>(&config).unwrap_or_default();
        camera_config.field_of_view = camera_settings.field_of_view.to_radians();
        camera_config.distance_multiplier = camera_settings.distance_multiplier;
        camera_config.render_distance_end = camera_settings.render_distance_end;
        camera_config.render_distance_start = camera_settings.render_distance_start;
        camera_config.aspect_ratio = camera_settings.aspect_width / camera_settings.aspect_height;
    }
    camera_config
}
#

Turns it in to actual Struct useable at runtime

#

not a solution to your current issue sadcat

rich moon
#

is it faster than ini?

#

or why are those better

unique iris
#

if something == "abcdefghijklmnop" {} -> if something {}

#

Wether the reading of the file itself is faster or slower is something i don't know, but you shouldn't be parsing files every frame either way.

#

I'd recommend making a trigger to do it once when WorldChrMan can't be found

#

but during runtime it's faster

#

and toml is kinda rust standard

#

i think

#

it's moreso Serde that i want to point out

#

Can of course, also use Notify that Axi linked

#

but idk wether it's beginner friendly, toml and serde are pretty easy to implement. You can just use #[derive(Deserialize)] instead of manually defining what the .ini should read.

#

@worn basin Do you have a detailed scenario of what you expect/want to happen and what is currently happening when you run your code?

worn basin
#

Yeah Ill write out the logic

#

On startup, make the .ini config file (working fine)
Settings:
HP regen enabled
HP regen level cap
HP regen max power

FP regen enabled
FP regen level cap
FP regen max power

In the thread/recurring task:

  1. Constantly check player's vigor and mind level
  2. Calculate the strength of regen that should be applied by doing max_power * (current_level / level_cap) - which should basically do a constant ramp-up of the regen strength as the level gets higher. (I want to add a section of code to limit the regen strength as right now, the regen strength will keep increasing as the player's level increases... i want it usable with solid uncapper, so stats would be able to go above 99)
  3. Lastly apply the regen at the calculated strength to the player's hp/fp
#

I wonder if there is anything wrong with the reading of the config file 🤔

#

Or if the player_game_data isnt the right module to reference

worn basin
#

And no regen effect occurs

unique iris
#

player_game_data.current_hp

What should this value be currently

#

1000.0?

worn basin
#

it should be constantly changing to be the player's current hp amount, changing on damage/heal

unique iris
#

oh wait it reads that from player data puddingcat

worn basin
#

At least thats my understanding... idk

unique iris
#

i thought it was a value taken from the .ini, my bad

worn basin
#

np

unique iris
#

was this working before?

#

or it hasn't worked at all, so far

worn basin
#

I havent seen any regen at all at any point

#

with this code

#

Only change ingame is the test damage

#

Cargo.toml if needed:

[package]
name = "passive_bonuses"
version = "0.1.0"
edition = "2024"

[dependencies]
eldenring = "0.13.0"
fromsoftware-shared = "0.13.0"
rust-ini = "0.21.3"
windows = {version ="0.62.2", features = ["Win32_System_LibraryLoader"]}

[lib]
crate-type = ["cdylib"]

unique iris
#

I think you should be using the ChrData module instead of the player game data

worn basin
#

where is that?

unique iris
#

Also, you can chain methods on structures to access fields

// Reference world_chr_man
let Ok(world_chr_man) = (unsafe { WorldChrMan::instance() }) else {
    return;
};

// Reference main_player from world_chr_man
let Some(ref mut main_player) = world_chr_man.main_player else {
    return;
};

// Reference player_game_data
let player_game_data = unsafe { &mut *main_player.player_game_data.as_mut() };
let Some(chr_data) = unsafe { WorldChrMan::instance() }
    // Convert Result<> in to Option<> so that ".and_then()" can be used.
    .ok()
    // If world_chr_man exists, immediately grab the main_player.
    // We can do this because "main_player" is an "Option<>".
    .and_then(|world_chr_man| world_chr_man.main_player.as_mut())
    // If the main player exists, grab a mutable reference to it's ChrDataModule.
    // We don't use ".and_then" because ".as_mut()" isn't an "Option<>".
    // It automatically "maps" the closure to "Some()".
    .map(|main_player| main_player.chr_ins.module_container.data.as_mut())
else {
    return;
};
            
worn basin
#

got it

#

will change when I have time and see what happens

#

Should be available somewhat soon tho

unique iris
south flare
#

player_game_data is nonnull here

unique iris
#

they need ChrDataModule, not PlayerGameDdata

worn basin
#

what should the use at the top, and the referencing in the middle look like?

#

to get the chrdatamodule

unique iris
#

Dont need anything besides WorldChrMan

worn basin
#

k

#

what should the code look like to reference the chrdatamodule through worldchrman?? Im having a hard time

#

// Reference chr data through world_chr_man
let Some(chr_data) = unsafe { WorldChrMan::instance() }
// Convert Result<> in to Option<> so that ".and_then()" can be used.
.ok()
// If world_chr_man exists, immediately grab the main_player.
// We can do this because "main_player" is an "Option<>".
.and_then(|world_chr_man| world_chr_man.main_player.as_mut())
// If the main player exists, grab a mutable reference to it's ChrDataModule.
// We don't use ".and_then" because ".as_mut()" isn't an "Option<>".
// It automatically "maps" the closure to "Some()".
.map(|main_player| main_player.chr_ins.module_container.data.as_mut())
else {
return;
};

#

?

unique iris
#

yes

worn basin
#

ooook

unique iris
#

instead of doing multiple

else { 
  return; 
}; 

it's obtained by stacking methods

worn basin
#

// Reference world_chr_man
let Ok(world_chr_man) = (unsafe { WorldChrMan::instance() }) else {
return;
};

can be removed since this other code takes care of it, right'

unique iris
#

yes

#

we aren't storing world_chr_man at all in the blob i posted, we immediately dive in to its contents till we have access to the CSChrDataModule

worn basin
#

// Reference player_game_data
let player_game_data = unsafe { &mut *main_player.player_game_data.as_mut() };

remove this too?

unique iris
#

yes

worn basin
#

ok

unique iris
worn basin
#

got it

#

so how do I get the player's vigor and mind? since there's no more reference to player_game_data?

#

or is there vigor/mind pub in one of those we have referenced now?

unique iris
#

ah i forgot you did math on the character stats

#

my bad

worn basin
#

well...

#

I could just do math with the player's max health?

unique iris
#

you can do that as well, but you totally can grab player game data and chr data module

worn basin
#

can you write out how to get the playergamedata too?

#

with the stacking methods?

#

or should it be separate?

unique iris
#

Well, the thing is, we can't have 2 references where 1 of them is mutable

#

A.e, i can't return 2 of them from the same method if we require .as_mut()

worn basin
#

ok

#

so separate?

unique iris
#

which we need to mutate the health or fp, etc

unique iris
#
let Some(main_player) = unsafe { WorldChrMan::instance() }
    .ok()
    .and_then(|world_chr_man| world_chr_man.main_player.as_mut())
else {
    return;
};

let player_game_data = main_player.player_game_data.as_ref();
let chr_data_module = main_player.chr_ins.module_container.data.as_mut();
worn basin
#

Current Code:

unique iris
#
let Some(main_player) = unsafe { WorldChrMan::instance() }
    .ok()
    .and_then(|world_chr_man| world_chr_man.main_player.as_mut())
else {
    return;
};

let player_game_data = unsafe { main_player.player_game_data.as_ref() };
let chr_data_module = main_player.chr_ins.module_container.data.as_mut();

// Calculate each buff's power at current related stat level
let hp_regen_current_power = settings.hp_regen_max_power * (player_game_data.vigor as f32 / settings.hp_regen_level_cap as f32);
let fp_regen_current_power = settings.fp_regen_max_power * (player_game_data.mind as f32 / settings.fp_regen_level_cap as f32);

// Apply passives at calculated power if enabled in settings (- is minus, + is add ???)
if settings.hp_regen_enabled == "true" {
    chr_data_module.hp = (player_game_data.current_hp as f32 + 1f32) as i32;
}

if settings.fp_regen_enabled == "true" {
    chr_data_module.fp = (player_game_data.current_fp as f32 + 1f32) as i32;
}

// test damage
chr_data_module.hp = (player_game_data.current_hp as f32 - 100f32) as i32;
chr_data_module.fp = (player_game_data.current_fp as f32 - 100f32) as i32;
#

ah, im on a newer version, i have a different wrapper on player_game_data

#

fixed

#

really curious if it's gonne be working now thinkrome

worn basin
#

ill finish updating with your code and try it out

#

if settings.hp_regen_enabled == "true" {
chr_data_module.hp = (player_game_data.current_hp as f32 + 1f32) as i32;
}

if settings.fp_regen_enabled == "true" {
chr_data_module.fp = (player_game_data.current_fp as f32 + 1f32) as i32;
}

did u mean to have the chr_data_module.XX on left and player_game_data.current.XX on right?

#

i was thinking this:

unique iris
#

puddingcat oversight, should both be chr_data_module.SomeVar

worn basin
#

👍

#

gonna try it out now

#

seems to be working! need to fine tune the power of the regen since its stronger than needed rn

#

then need to test more

unique iris
#

If you lessen the values and someone comes in with uncapped fps the floating point might become too small for the math to do anything

#

Since you're casting back and forth

worn basin
#

ok

#

One thing im wondering is there a module somewhere that controls walk/run speed and jump height? And sorcery/incantation damage buff that could be used easily? That's what i was planning for me to try to add after I fix the problems i was having

#

So there can be buff for dex, str, int, fai

unique iris
#

Animation speed is in behavior iirc

#

but i don't think you're looking for that

#

You can use the physics module to adjust jump heights

#

That will require a bunch of game dev math

worn basin
#

ok

#

i will check it out. Thanks for all the help!!

unique iris
#

np, glad it's working lizParty

worn basin
#

SOLVED - DLL Mod made in Rust not running recurring task right?

worn basin
#

Here it is in all its glory!