Thresholds

A town's price thresholds are updated by the update_town_price_thresholds function at 0x00528070 every time the town ticks.

The calculation is partially understood, but some aspects are still to be determined. The following pseudocode denotes what is known:

#![allow(unused)]
fn main() {
fn update_town_price_thresholds(town) {
    let mut thresholds = [[0; 4]; 24];
    let mut building_material_factor = 1;
    let mut extra_demand_bitmask = 0;
    let mut t1_days = 14;

    // Consider the town's situation
    if town.flags & (TOWN_FLAG_SIEGE | TOWN_FLAG_BLOCKADE | TOWN_FLAG_PIRATE_ATTACK | TOWN_FLAG_FROZEN) != 0 {
        t1_days = 28;
        building_material_factor = 2;
        extra_demand_bitmask = ware_threshold_bitmask_grain |
            ware_threshold_bitmask_meat |
            ware_threshold_bitmask_fish |
            ware_threshold_bitmask_beer |
            ware_threshold_bitmask_salt |
            ware_threshold_bitmask_honey |
            ware_threshold_bitmask_spices |
            ware_threshold_bitmask_wine |
            ware_threshold_bitmask_timber;
    } else {
        if town.flags & TOWN_FLAG_FIRE != 0 {
            extra_demand_bitmask = ware_threshold_bitmask_grain |
                ware_threshold_bitmask_meat |
                ware_threshold_bitmask_fish |
                ware_threshold_bitmask_beer |
                ware_threshold_bitmask_salt |
                ware_threshold_bitmask_honey |
                ware_threshold_bitmask_spices |
                ware_threshold_bitmask_wine |
                ware_threshold_bitmask_timber;
        } else {
            if town.flags & TOWN_FLAG_FAMINE != 0 {
                extra_demand_bitmask = ware_threshold_bitmask_grain |
                    ware_threshold_bitmask_meat |
                    ware_threshold_bitmask_fish |
                    ware_threshold_bitmask_beer |
                    ware_threshold_bitmask_salt |
                    ware_threshold_bitmask_honey |
                    ware_threshold_bitmask_spices |
                    ware_threshold_bitmask_wine;
            } else if own.flags & TOWN_FLAG_FAMINE != 0 {
                extra_demand_bitmask = ware_threshold_bitmask_grain |
                    ware_threshold_bitmask_meat |
                    ware_threshold_bitmask_fish |
                    ware_threshold_bitmask_beer |
                    ware_threshold_bitmask_salt |
                    ware_threshold_bitmask_honey |
                    ware_threshold_bitmask_spices;
            }
        }
    }

    // Initialize t0 and t1 (for normale wares except bricks)
    for (_, threshold) in thresholds.iter_mut().enumerate().take(19) {
        let t0_days = if extra_demand_bitmask & 1 != 0 { 14 } else { 7 };
        threshold[0] = t0_days;
        threshold[1] = t0_days + t1_days;
        extra_demand_bitmask >>= 1;
    }

    // Initialize t0 and t1 for the rest
    thresholds[WareId::Bricks][0] = 0;
    thresholds[WareId::Brick][1] = 0;
    thresholds[WareId::Sword][0] = 0;
    thresholds[WareId::Sword][1] = 0;
    thresholds[WareId::Bow][0] = 0;
    thresholds[WareId::Bow][1] = 0;
    thresholds[WareId::Crossbow][0] = 0;
    thresholds[WareId::Crossbow][1] = 0;
    thresholds[WareId::Carbine][0] = 0;
    thresholds[WareId::Carbine][1] = 0;

    // Grain t1 bonus
    thresholds[WareId::Grain][1] += t1_days;

    // Scale t0 and t1 with consumption
    for (i, threshold) in thresholds.iter_mut().enumerate().take(20) {
        let consumption = town.consumption_businesses[i]
            + town.consumption_citizens[i]
            + 1;
        threshold[0] *= consumption;
        threshold[1] *= consumption;
    }

    // Siege pitch bonus
    if town.flags & TOWN_FLAG_SIEGE {
        thresholds[WareId::Pitch][0] += 7 * town.get_siege_pitch_consumption();
        thresholds[WareId::Pitch][1] += 14 * town.get_siege_pitch_consumption();
    }

    // Seasonal t1 boni
    match now().month {
        Month::September => { // 0.15
            thresholds[WareId::Grain][1] += thresholds[WareId::Grain][1] * 3 / 20;
            thresholds[WareId::Wine][1] += thresholds[WareId::Wine][1] * 3 / 20;
        }
        Month::October => { // 0.3
            thresholds[WareId::Grain as usize][1] += thresholds[WareId::Grain as usize][1] * 3 / 10;
            thresholds[WareId::Wine as usize][1] += thresholds[WareId::Wine as usize][1] * 3 / 10;
        }
        Month::November => { // 0.2
            thresholds[WareId::Grain as usize][1] += thresholds[WareId::Grain as usize][1] / 5;
            thresholds[WareId::Wine as usize][1] += thresholds[WareId::Wine as usize][1] / 5;
        }
        Month::December => { // 0.1
            thresholds[WareId::Grain as usize][1] += thresholds[WareId::Grain as usize][1] / 10;
            thresholds[WareId::Wine as usize][1] += thresholds[WareId::Wine as usize][1] / 10;
        }
        _ => {}
    }

    // Building material boni
    thresholds[WareId::Pitch][0] += 1800;
    thresholds[WareId::Bricks][0] += 80000 * building_material_factor;
    thresholds[WareId::Timber][0] += 42000;
    thresholds[WareId::Timber][1] += 84000;
    thresholds[WareId::Bricks][1] += 160000 * building_material_factor;
    thresholds[WareId::Pitch][1] += 3600;
    thresholds[WareId::Hemp][0] += 5000 * building_material_factor;
    thresholds[WareId::IronGoods][0] += 2000 * building_material_factor;
    thresholds[WareId::IronGoods][1] += 4000 * building_material_factor;

    /*
    TODO: Weapons
    let mut scaled_citizens = 10 * town.citizens_total / 200;
    if town.flags & (TOWN_FLAG_SIEGE | TOWN_FLAG_PIRATE_ATTACK) != 0 {
        scaled_citizens *= 2;
    }
    */

    // Set t2 and t3 except for bricks and weapons
    for i in 0..19 {
        thresholds[i][2] = thresholds[i][1] + 10 * town.unidentified_array[i];
        thresholds[i][3] = thresholds[i][2] + thresholds[i][0];
    }

    // Pitch and bricks production bonus
    if town.production[WareId::Pitch] > 0 {
        thresholds[WareId::Pitch][3] += 3600;
    }
    if town.production[WareId::Bricks] > 0 {
        thresholds[WareId::Bricks][3] += 160000;
    }

    // Clothes bonus
    thresholds[WareId::Cloth][0] += 400;
    thresholds[WareId::Cloth][1] += 800;
    thresholds[WareId::Cloth][2] += 800;
    thresholds[WareId::Cloth][3] += 1600;

    // Bricks t2 and t3
    if has_effective_bricks_production {
        thresholds[WareId::Bricks][2] = thresholds[WareId::Bricks][1] + town.unidentified_array[WareId::Bricks];
        thresholds[WareId::Bricks][3] = thresholds[WareId::Bricks][1] + 2 * town.unidentified_array[WareId::Bricks];
    } else if thresholds[WareId::Bricks][2] > 2 * thresholds[WareId::Bricks][1] {
        // TODO: can this every be true?
        thresholds[WareId::Bricks][2] = 2 * thresholds[WareId::Bricks][1];
        thresholds[WareId::Bricks][3] = 3 * thresholds[WareId::Bricks][1];
    }

    // Enforce minima
    for (i, threshold) in thresholds.iter_mut().enumerate().take(20) {
        let ware_id = WareId::from_usize(i).unwrap();
        let minimum = if ware_id.is_barrel_ware() {
            1000
        } else {
            10000
        };
        if threshold[0] < minimum {
            threshold[0] = minimum;
        }
        let mut min = minimum + minimum;
        for j in 1..4 {
            if threshold[j] < min {
                threshold[j] = min;
            }
            if threshold[j] <= threshold[j - 1] {
                threshold[j] = threshold[j - 1] + minimum;
            }

            min += minimum;
        }
    }
}
}