Fixed Objective Specific Behaviors, Added Mii Bounding Fixes, etc.#2
Fixed Objective Specific Behaviors, Added Mii Bounding Fixes, etc.#2JimKatz wants to merge 3 commits into
Conversation
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);
}
}
}
|
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
left a comment
There was a problem hiding this comment.
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.
| 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;} |
There was a problem hiding this comment.
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;
}There was a problem hiding this comment.
Oh thanks, I got an error when trying to do that the first time I added it.
| 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)), | ||
| }; | ||
| } |
There was a problem hiding this comment.
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 { <...> }
}There was a problem hiding this comment.
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"
There was a problem hiding this comment.
okay, but there are some cases farther down where you force the behavior to be 1, should those have instead been 0 or 1?
There was a problem hiding this comment.
That's for the "Find the odd mii out" objectives. They require the mii behavior to be "1"
| 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; | ||
|
|
||
| } |
There was a problem hiding this comment.
shouldn't this already be handled by some code above? If it isn't, then it should be merged into the code above.
There was a problem hiding this comment.
Yeah, but it didn't work until I added this. Some level types only work with certain behaviors.
There was a problem hiding this comment.
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.)
There was a problem hiding this comment.
yeah, that might be a good idea
Fixed just about every bug I have encountered
Fixed/added a lot of stuff