Skip to content

Fixed Objective Specific Behaviors, Added Mii Bounding Fixes, etc.#2

Open
JimKatz wants to merge 3 commits into
kitlith:masterfrom
JimKatz:patch-1
Open

Fixed Objective Specific Behaviors, Added Mii Bounding Fixes, etc.#2
JimKatz wants to merge 3 commits into
kitlith:masterfrom
JimKatz:patch-1

Conversation

@JimKatz

@JimKatz JimKatz commented Apr 25, 2020

Copy link
Copy Markdown

Fixed/added a lot of stuff

use serde::{Serialize, Deserialize};
use std::fs::File;
use std::path::PathBuf;
use std::io::{Read, Write};
use byteorder::{BigEndian, ByteOrder};
use structopt::StructOpt;
use rand::prelude::*;
use rand::distributions::uniform::Uniform;
use rand::SeedableRng;

#[derive(Serialize, Deserialize, Default, Debug)]
struct Level {
    num_miis: u32,
    behavior: u32,
    level_type: u32,
    map: u32,
    zoom_out_max: f32,
    zoom_in_max: f32,
    unk7: f32,
    horiz_dist: f32,
    vert_dist: f32,
    darkness: f32,
    head_size: f32,
    unk12: f32,
    unk13: f32,
    unk14: f32,
    unk15: f32,
    unk16: f32
}

impl Level {
    fn from_bytes(input: &[u8; 64]) -> Level {
        Level {
            num_miis: BigEndian::read_u32(&input[0..]),
            behavior: BigEndian::read_u32(&input[4..]),
            level_type: BigEndian::read_u32(&input[8..]),
            map: BigEndian::read_u32(&input[12..]),
            zoom_out_max: BigEndian::read_f32(&input[16..]),
            zoom_in_max: BigEndian::read_f32(&input[20..]),
            unk7: BigEndian::read_f32(&input[24..]),
            horiz_dist: BigEndian::read_f32(&input[28..]),
            vert_dist: BigEndian::read_f32(&input[32..]),
            darkness: BigEndian::read_f32(&input[36..]),
            head_size: BigEndian::read_f32(&input[40..]),
            unk12: BigEndian::read_f32(&input[44..]),
            unk13: BigEndian::read_f32(&input[48..]),
            unk14: BigEndian::read_f32(&input[52..]),
            unk15: BigEndian::read_f32(&input[56..]),
            unk16: BigEndian::read_f32(&input[60..]),
        }
    }

    fn to_bytes(&self, output: &mut [u8; 64]) {
        BigEndian::write_u32(&mut output[0..], self.num_miis);
        BigEndian::write_u32(&mut output[4..], self.behavior);
        BigEndian::write_u32(&mut output[8..], self.level_type);
        BigEndian::write_u32(&mut output[12..], self.map);
        BigEndian::write_f32(&mut output[16..], self.zoom_out_max);
        BigEndian::write_f32(&mut output[20..], self.zoom_in_max);
        BigEndian::write_f32(&mut output[24..], self.unk7);
        BigEndian::write_f32(&mut output[28..], self.horiz_dist);
        BigEndian::write_f32(&mut output[32..], self.vert_dist);
        BigEndian::write_f32(&mut output[36..], self.darkness);
        BigEndian::write_f32(&mut output[40..], self.head_size);
        BigEndian::write_f32(&mut output[44..], self.unk12);
        BigEndian::write_f32(&mut output[48..], self.unk13);
        BigEndian::write_f32(&mut output[52..], self.unk14);
        BigEndian::write_f32(&mut output[56..], self.unk15);
        BigEndian::write_f32(&mut output[60..], self.unk16);
    }

    fn from_file(mut input: File) -> Vec<Level> {
        let mut levels: Vec<Level> = Vec::new();
        let mut lvl_bytes = [0u8;64];
        while input.read_exact(&mut lvl_bytes).is_ok() {
            levels.push(Level::from_bytes(&lvl_bytes));
        }
        levels
    }

    fn to_file(mut output: File, levels: Vec<Level>) {
        let mut lvl_bytes = [0u8;64];
        for level in levels {
            level.to_bytes(&mut lvl_bytes);
            output.write_all(&lvl_bytes).unwrap();
        }
    }
}

#[derive(Debug, StructOpt)]
enum Args {
    Assemble {
        input: PathBuf,
        output: Option<PathBuf>
    },
    Disassemble {
        #[structopt(long, short)]
        compact: bool,
        input: PathBuf,
        output: Option<PathBuf>
    },
    Randomize {
        input: PathBuf,
        output: PathBuf,
        #[structopt(long, short)]
        seed: Option<u64>
        // TODO: let the parameters be tweaked
    }
}

fn main() {
    let args = Args::from_args();

    match args {
        Args::Assemble {input, output} => {
            let mut input_file = File::open(&input).unwrap();
            let levels: Vec<Level> = serde_json::from_reader(input_file).unwrap();

            let mut favorite_pending = false;
            for (idx, level) in levels.iter().enumerate() {
                match level.level_type {
                    6 if !favorite_pending => favorite_pending = true,
                    6 if favorite_pending => println!("Warning: level index {} is of type 'pick your favorite' after another 'pick your favorite' level. Game will crash.", idx),
                    7 if !favorite_pending => println!("Warning: level index {} is of type 'find your favorite' without a preceeding 'pick your favorite' level. Game will crash.", idx),
                    7 if favorite_pending => favorite_pending = false,
                    9  | 10 | 11 if level.behavior != 1 && level.behavior != 3 =>
                        println!("Warning: level index {} has an objective that requires mii behaviors 1 or 3 to function properly, but is set to {}", idx, level.behavior),
                    17 | 18 | 19 if level.behavior != 0 =>
                        println!("Warning: level index {} has an objective that requires mii behavior 0 to function properly, but is set to {}", idx, level.behavior),
                    _ => {}
                }

                let max_miis = match level.map {
                    4 => 40,
                    _ => 99
                };

                if level.num_miis > max_miis {
                    println!("Warning: level index {} has more than the maximum of {} miis for this level type.", idx, max_miis);
                }
            }

            if favorite_pending {
                println!("Warning: there is no matching 'find your favorite' level to a 'pick your favorite' level, and we've reached the end of the file. Game will crash.");
            }

            let output = output.unwrap_or_else(|| input.with_extension("bin"));
            let mut output = File::create(output).unwrap();

            Level::to_file(output, levels);
        },
        Args::Disassemble {compact, input, output} => {
            let levels = Level::from_file(File::open(&input).unwrap());

            let output = output.unwrap_or_else(|| input.with_extension("json"));
            let output = File::create(output).unwrap();
            if compact {
                serde_json::to_writer(output, &levels).unwrap();
                //println!("{}", toml::to_string(&levels).unwrap());
            } else {
                serde_json::to_writer_pretty(output, &levels).unwrap();
                //println!("{}", toml::to_string_pretty(&levels).unwrap());
            }
        },
        Args::Randomize {input, output, seed} => {
            let mut levels = Level::from_file(File::open(&input).unwrap());

            let seed = seed.unwrap_or_else(|| random());
            println!("Using seed: {}", seed);
            let mut rng = SmallRng::seed_from_u64(seed);

            let mut favorite_pending = false;

            let last_idx = levels.len() - 1;
            for (i, mut level) in levels.iter_mut().enumerate() {
                level.level_type = if (i == last_idx) {
                    if favorite_pending {
                        favorite_pending = false;
                        7
                    } else {
                        // avoid generating 6 or 7
                        let mut level_type = rng.sample(Uniform::new_inclusive(1, 19));
                        if level_type > 5 {
                            level_type += 2;
                        }
                        level_type
                    }
                } else {
                    let level_type = rng.sample(Uniform::new_inclusive(1, 21));
                    if level_type == 6 || level_type == 7 {
                        // special handling for levels dealing with favorites:
                        if favorite_pending {
                            favorite_pending = false;
                            7
                        } else {
                            favorite_pending = true;
                            6
                        }
                    } else {
                        level_type
                    }
                };

                let max_miis = match level.map {
                    4 => 40,
                    _ => 90
                };
				
// This is where the magic happens!
                
                level.num_miis = rng.sample(Uniform::new_inclusive(4, max_miis));
				
				level.unk12 = 0.0;
				level.unk13 = 0.0;
				level.unk14 = 0.0;
				level.unk15 = 0.0;
				level.unk16 = 0.0;
                level.behavior = match level.level_type {
                    9  | 10 | 5 | 11 | 20 => if rng.gen_ratio(1, 2) { 1 } else { 3 } //Find The Odd Mii(s) out
                    17 | 18 | 19 => 0, // Find The Insomniac 
					14 | 15 | 16 => 0, // Find The Sleepyhead
					12 | 13 => if rng.gen_ratio(1, 2) { 4 } else { 2 }
                    _ => rng.sample(Uniform::new_inclusive(0, 6)),
                };


                level.map = rng.sample(Uniform::new_inclusive(0, 4));
				if level.map == 4 {
				level.behavior = rng.sample(Uniform::new_inclusive(0, 1));
}
	
                 if level.map == 1 {
                 level.behavior = match level.map {
	             1 => if rng.gen_ratio(1, 4) { 0 } else if rng.gen_ratio(1, 2) { 1 } else { 4 }
				 4 => if rng.gen_ratio(1, 2) { 0 } else { 1 } //Elevator Behavior Softlock Fix
				 3 => if rng.gen_ratio(1, 2) { 0 } else { 1 } //Tennis Stands Behavior Softlock Fix
	             _ => rng.sample(Uniform::new_inclusive(4, 5)),
                };
	}
                level.zoom_out_max = rng.sample(Uniform::new_inclusive(-406.0, -135.0));
                level.zoom_in_max = rng.sample(Uniform::new_inclusive(-135.0, -22.0));

                level.darkness = if rng.gen_ratio(1, 2) {
                    0.0 // 50% chance for no darkness
                } else {
                    rng.sample(Uniform::new_inclusive(84.0, 110.0))
                };
                level.head_size = rng.sample(Uniform::new_inclusive(1.35, 3.5));
				if level.darkness > 1.0
				{level.unk12 = 3.0;}
				{level.unk13 = 10.0;}
				{level.unk14 = 1.0;}
				{level.unk15 = 0.0;}
				{level.unk16 = 6.0;}
				
				if level.map == 3 { 
				level.zoom_out_max = if rng.gen_ratio(1, 2) { -279.60767 } else { -187.60767 };
				level.zoom_in_max = -279.60767;
				}
				
				if level.map == 3 { 
				level.horiz_dist = 4.7;
				level.vert_dist = 22.4;
				}
				
				if level.map == 4 { 
				level.unk7 = 35.0;
				
				}
				
			    if level.map == 2 {
				level.unk7 = 0.0;
				
				
				}
				if level.map == 3 {
				level.unk7 = -60.0;
				
				}
				//Street Mii Bounding Fixes
			    if level.map == 0 { 
                 level.zoom_out_max = if rng.gen_ratio(3, 4) { -199.0 } else { -205.0 };
				 level.zoom_in_max = if rng.gen_ratio(3, 4) { -199.0 } else { -205.0 };
			     level.horiz_dist = 35.0;
				  level.vert_dist = 97.0;
				}
				//Ocean Mii Bounding Fixes
			    if level.map == 1 { 
                 level.zoom_out_max = if rng.gen_ratio(3, 4) { -180.0 } else { -240.0 };
				 level.zoom_in_max = if rng.gen_ratio(3, 4) { -180.0 } else { -240.0 };
			     level.horiz_dist = if rng.gen_ratio(3, 4) { 30.0 } else { 40.0 };
				  level.vert_dist = if rng.gen_ratio(3, 4) { 90.0 } else { 113.0 };
			
				}
				//Better safe than sorry
				// If you have too many miis on the Escalator the game will crash
		        if level.map == 4 { // If Escalator Stage
                level.num_miis = rng.sample(Uniform::new_inclusive( 8, 40)); // Nothing more then 40 miis
				
				}
				if level.level_type == 5 {
				
				level.behavior = 1;
				}
				
				if level.level_type == 9 {
				level.behavior = 1;
				
				}
				
				if level.level_type == 10 {
				level.behavior = 1;
				
				}
				
				if level.level_type == 11 {
				level.behavior = 1;
				
				}
				
				if level.level_type == 20 {
				level.behavior = 1;
				
				}
				
                 if level.map == 3 && (level.level_type == 17 || level.level_type == 18 || level.level_type == 19) {
				 level.darkness = 0.0; // Darkness doesn't work on Find The Insomniac Stages on the Tennis Stands

				}
				
				 if level.map == 3 && (level.level_type == 12 || level.level_type == 13) { //Fastest Mii Fix on Tennis Stands
				 level.behavior = 3;
                 level.level_type == 2;

				}
				
				if level.level_type == 14 {
				level.behavior = if rng.gen_ratio(1, 2) { 1 } else { 0 }
				
				}
								
				if level.level_type == 15 {
				level.behavior = if rng.gen_ratio(1, 2) { 1 } else { 0 }
				
				}
								
				if level.level_type == 16 {
				level.behavior = if rng.gen_ratio(1, 2) { 1 } else { 0 }
				
				}
				

                 if level.map == 0 && (level.level_type == 14 || level.level_type == 15 || level.level_type == 16) {
				 level.darkness = 0.0; // Darkness doesn't work on Find the Sleepyhead Stages on the Street
				 
				 }
				 
				 if level.map == 0 && (level.zoom_out_max > -199.0) {  // Street Non Zoom Fixes
				  level.zoom_out_max = -222.0;
				   level.zoom_in_max = -222.0;
				      level.horiz_dist = 4.5;
				        level.vert_dist = 35.4;
				 


				}
				
				if level.map == 0 && (level.behavior == 2 || level.behavior == 4 || level.behavior == 6 || level.level_type == 14 || level.level_type == 15 || level.level_type == 16) {
				 level.zoom_out_max = -222.0;
				 level.zoom_in_max = -222.0;
				 level.horiz_dist = if rng.gen_ratio(1, 2) { 33.4 } else { 57.0 };
				  level.vert_dist = 57.4;
				 


				}
				
				
				if level.map == 1 {
				level.darkness = 0.0; // Darkness doesn't work on Ocean Stages


				
				}
				
				
				 if level.map == 3 && (level.behavior == 3 || level.behavior == 4 || level.behavior == 6) {
				  level.behavior = 1;
				
					
				 }
				 
	              if level.map == 0 && (level.zoom_out_max == -205.0 || level.zoom_in_max == -199.0) {
				      level.horiz_dist = 22.4;
				        level.vert_dist = 64.0;
				 
				 }
			
			        if level.map == 1 && (level.zoom_out_max == -240.0) {
			        level.zoom_in_max = -180.0;
				        level.horiz_dist = 15.0;
				        level.vert_dist = 72.0;
				 
				 }
				 //Find the Sleepyhead Tennis Stands Fix
				 if level.map == 3 && (level.level_type == 14 || level.level_type == 15 || level.level_type == 16) {
				 level.level_type = 21;
			     level.behavior = 1;
				 
				 
				 }
				 //Fastest Mii Fix Tennis Fix
				 if level.map == 3 && (level.level_type == 12 || level.level_type == 13) {
				 	 level.level_type = 21;
				      level.behavior = 0;
				 
				 
				 
				 }
				 //Odd Mii Out Fix Tennis Stands Fix
				 	if level.map == 3 && (level.level_type == 9 || level.level_type == 10 || level.level_type == 11 || level.level_type == 20) {
				     level.behavior = 1;
				 
				 
				 }
				 
				 if level.map == 2 { 
				level.darkness = if rng.gen_ratio(1, 3) {
                    0.0 // 50% chance for no darkness
                } else {
                    rng.sample(Uniform::new_inclusive(94.9, 135.0))
				 
				 
				 };
				 
				 
		 }
		 //Insomniac Fix Tennis Stands
				 if level.map == 3 && (level.level_type == 17 || level.level_type == 18 || level.level_type == 19) {
				 level.level_type = 10;
			     level.behavior = 1;
				 
				 
				 }
				 //Street Non Zoom Level Fix for Miis
				 if level.map == 0 && (level.zoom_out_max == -205.0 || level.zoom_in_max == -205.0) {
				  level.horiz_dist = 4.7;
				  level.vert_dist = 24.0;
				 
				 }
				 
				  if level.map == 1 && (level.zoom_out_max == -180.0 || level.zoom_in_max == -180.0) {
				  level.horiz_dist = 10.2;
				  level.vert_dist = 17.4;
				 
				}
			
			 if level.map == 0 && (level.behavior == 5) {
				  level.behavior = 1;
				
				}
				
			 if level.map == 0 && (level.zoom_out_max == -199.0 || level.zoom_in_max == -199.0) {
				  level.horiz_dist = 4.0;
				  level.vert_dist = 11.0;
				
				}
				
				 if level.map == 0 && (level.horiz_dist == 10.2 || level.vert_dist == 17.4) {
				  level.horiz_dist = 22.4;
				  level.vert_dist = 64.0;
				  level.zoom_in_max = -205.0;
				
				}
				
				if level.map == 2 && (level.level_type == 12 || level.level_type == 13) {
				  level.horiz_dist = 16.3;
				  level.vert_dist = 12.8;				  
				}
				//Darkness Find The Odd Mii Out fix on Space Stages. Which doesnt work for some reason
				if level.map == 2 && (level.level_type == 9 || level.level_type == 10 || level.level_type == 11) {
				 level.darkness = 0.0;
				
				
				}

				
 } 

            let output = File::create(output).unwrap();
            Level::to_file(output, levels);
        }
    }

}
@kitlith

kitlith commented Apr 25, 2020

Copy link
Copy Markdown
Owner

Um? why'd you paste source code into the commit message?

In general, I was trying to go for a style where each variable is only assigned once and rng is only called if the field is actually random, so I'd like to see that changed (lmk if you'd prefer I just did that myself)

I'm gonna do a more detailed review in a little bit.

@kitlith kitlith left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might leave some more comments later. I'm having a hard time understanding your later code because your indentation is inconsistent. It might be worth running rustfmt on it.

The long and short of it is that you've got a ton of if statements doing different things down below, and I'd like to consolidate that where possible.

Comment thread src/main.rs Outdated
Comment on lines +249 to +254
if level.darkness > 1.0
{level.unk12 = 3.0;}
{level.unk13 = 10.0;}
{level.unk14 = 1.0;}
{level.unk15 = 0.0;}
{level.unk16 = 6.0;}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probably does not do what you want it to. This acts like:

if level.darkness > 1.0 {
    level.unk12 = 3.0;
}
level.unk13 = 10.0;
level.unk14 = 1.0;
level.unk15 = 0.0;
level.unk16 = 6.0;

instead of what you probably meant:

if level.darkness > 1.0 {
    level.unk12 = 3.0;
    level.unk13 = 10.0;
    level.unk14 = 1.0;
    level.unk15 = 0.0;
    level.unk16 = 6.0;
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh thanks, I got an error when trying to do that the first time I added it.

Comment thread src/main.rs
Comment on lines 218 to +239
level.behavior = match level.level_type {
9 | 10 | 11 => if rng.gen_ratio(1, 2) { 1 } else { 4 }
17 | 18 | 19 => 0,
9 | 10 | 5 | 11 | 20 => if rng.gen_ratio(1, 2) { 1 } else { 3 } //Find The Odd Mii(s) out
17 | 18 | 19 => 0, // Find The Insomniac
14 | 15 | 16 => 0, // Find The Sleepyhead
12 | 13 => if rng.gen_ratio(1, 2) { 4 } else { 2 }
_ => rng.sample(Uniform::new_inclusive(0, 6)),
};


level.map = rng.sample(Uniform::new_inclusive(0, 4));
if level.map == 4 {
level.behavior = rng.sample(Uniform::new_inclusive(0, 1));
}

if level.map == 1 {
level.behavior = match level.map {
1 => if rng.gen_ratio(1, 4) { 0 } else if rng.gen_ratio(1, 2) { 1 } else { 4 }
4 => if rng.gen_ratio(1, 2) { 0 } else { 1 } //Elevator Behavior Softlock Fix
3 => if rng.gen_ratio(1, 2) { 0 } else { 1 } //Tennis Stands Behavior Softlock Fix
_ => rng.sample(Uniform::new_inclusive(4, 5)),
};
}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should try and combine these settings of level.behavior, because each one ignores any constraints of the previous one.

i.e., after moving the generation of map above the generation of behavior:

level.behavior = match level.level_type {
    5 | 9 | 10 | 11 | 20 => if let 3 | 4 = level.map {
        1 // this is the only behavior possible with this combination of level_type and map
    } else if rng.gen_ratio(1, 2) { 1 } else { 4 },
    12 | 13 => <...>, // TODO: we need to prevent level_type from being 12 or 13 and map from being 3 or 4 at the same time.
    14 | 15 | 16 | 17 | 18 | 19 => 0, // TODO: are there any maps/level_types that cannot accept a behavior of 0?
    _ if level.map == 1 => <...,>,
    _ => if let 3 | 4 = level.map { <...> } else { <...> }
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, it was a real mess when I tried to edit and copy over the existing syntax. Also every stage can use behavior type "0"

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

okay, but there are some cases farther down where you force the behavior to be 1, should those have instead been 0 or 1?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's for the "Find the odd mii out" objectives. They require the mii behavior to be "1"

Comment thread src/main.rs
Comment on lines +301 to +324
if level.level_type == 5 {

level.behavior = 1;
}

if level.level_type == 9 {
level.behavior = 1;

}

if level.level_type == 10 {
level.behavior = 1;

}

if level.level_type == 11 {
level.behavior = 1;

}

if level.level_type == 20 {
level.behavior = 1;

}

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this already be handled by some code above? If it isn't, then it should be merged into the code above.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, but it didn't work until I added this. Some level types only work with certain behaviors.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's probably because you're setting it up there, and then overwriting it by setting the level behavior again.

Should I try to make it so that we have a list of possible values that we remove from? (i.e. every loop we start with a behavior map or something, and then for level type 10 we remove all behaviors except 1, etc.)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, that might be a good idea

JimKatz added 2 commits June 18, 2020 01:31
Fixed just about every bug I have encountered
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants