Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src-tauri/crates/db/src/migrations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ pub fn ensure_compatible_schema(conn: &Connection) -> rusqlite::Result<()> {
}

/// Number of migrations defined. Keep in sync with the vec in `all_migrations`.
pub const MIGRATION_COUNT: usize = 51;
pub const MIGRATION_COUNT: usize = 52;

/// All migrations for a per-save game database.
/// Each save `.db` file gets this schema applied via `rusqlite_migration`.
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/crates/ofm_core/src/generator/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ pub fn generate_world(
mod tests {
use super::data::{NATIONALITY_POOLS, TEAM_TEMPLATES};
use super::*;
use domain::stats::{LolRole, Position};
use domain::stats::LolRole;

#[test]
fn test_generate_world_team_count() {
Expand Down
2 changes: 2 additions & 0 deletions src-tauri/crates/ofm_core/src/generator/world_io.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,5 +195,7 @@ mod tests {

let json = export_world_to_json(&world).unwrap();
let reparsed: WorldData = serde_json::from_str(&json).unwrap();

assert_eq!(reparsed.teams[0].country, "GB");
}
}
3 changes: 3 additions & 0 deletions src-tauri/crates/ofm_core/src/narrative/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ pub enum PersonaType {
pub enum SocialTone {
Professional,
Analytical,
Calm,
Community,
Close,
Dramatic,
Expand Down Expand Up @@ -371,6 +372,7 @@ fn is_real_persona_safe_tone(tone: SocialTone) -> bool {
tone,
SocialTone::Professional
| SocialTone::Analytical
| SocialTone::Calm
| SocialTone::Community
| SocialTone::Close
)
Expand All @@ -381,6 +383,7 @@ impl SocialTone {
match self {
SocialTone::Professional => "professional",
SocialTone::Analytical => "analytical",
SocialTone::Calm => "calm",
SocialTone::Community => "community",
SocialTone::Close => "close",
SocialTone::Dramatic => "dramatic",
Expand Down
2 changes: 1 addition & 1 deletion src-tauri/crates/ofm_core/src/player_rating.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ fn critical_penalty(player: &Player, _role: &LolRole) -> f64 {
#[cfg(test)]
mod tests {
use super::*;
use domain::player::{PlayerAttributes, Position};
use domain::player::PlayerAttributes;

fn make_player(role: LolRole) -> Player {
let attrs = PlayerAttributes {
Expand Down
40 changes: 20 additions & 20 deletions src-tauri/crates/ofm_core/src/random_events/message_builders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -143,25 +143,25 @@ pub(super) fn media_story_message(
match variant {
0 => (
format!(
"Esportmaníacos praises {} — the desk is unanimous",
"Esportmaníacos praises {}'s Rift control — the desk is unanimous",
player_name
),
format!(
"The Esportmaníacos panel spent a good chunk of today's stream on {} at {}.\n\n\
\"Nothing to criticize this week. The guy is absolutely dominating.\" \
The positive coverage should give confidence a boost in the locker room.",
\"Nothing to criticize this week. The guy is absolutely dominating the Rift: objective setups, draft execution, solo queue discipline, everything.\" \
The positive coverage should give confidence a boost in the team room.",
player_name, team_name
),
"be.msg.esportmaniacos.positive.subject0",
),
_ => (
format!(
"Esportmaníacos: {} is carrying {} this split",
"Esportmaníacos: {} is carrying {} through every Draft",
player_name, team_name
),
format!(
"The Esportmaníacos tertulianos rarely agree on anything, but {} got a full pass today: \
\"Consistent, solid, no dips in form. One of the best players in the league right now. \
\"Consistent, solid, no dips in form. From draft reads to objective setups, one of the best players in the league right now. \
{} should do everything to keep him.\"\n\n\
Good for morale — and for the market value.",
player_name, team_name
Expand All @@ -173,26 +173,26 @@ pub(super) fn media_story_message(
match variant {
0 => (
format!(
"Esportmaníacos goes after {} — panel shows no mercy",
"Draft Pressure on {} — panel shows no mercy",
player_name
),
format!(
"The Esportmaníacos panel did not hold back today on {}: \
\"The guy has completely disappeared. What happened to him this split? \
\"The guy has completely disappeared under lane pressure. What happened to him this split? \
{} can't keep relying on a player performing like this.\"\n\n\
This kind of coverage tends to hit morale hard. Worth having a word with the player.",
This kind of Rift coverage tends to hit morale hard. Worth having a word with the player after scrim review.",
player_name, team_name
),
"be.msg.esportmaniacos.negative.subject0",
),
_ => (
format!(
"Esportmaníacos questions {}'s consistency at {}",
"Pressure Watch: Esportmaníacos questions {}'s consistency at {}",
player_name, team_name
),
format!(
"Today's Esportmaníacos tertulianos session turned into a full breakdown of {}'s recent form. \
The verdict: \"Inconsistent. No regularity. Some days at top level, others completely invisible. \
The verdict: \"Inconsistent in lane and shaky in scrim patterns. No regularity. Some days at top level, others completely invisible on the Rift. \
{} deserves better output from a player in that role.\"\n\n\
Keep an eye on the player's morale.",
player_name, team_name
Expand Down Expand Up @@ -269,26 +269,26 @@ pub fn build_media_story_from_narrative(
match variant {
0 => (
format!(
"Esportmaníacos praises {} — the desk is unanimous",
"Esportmaníacos praises {}'s Rift control — the desk is unanimous",
player_name
),
format!(
"The Esportmaníacos panel spent a good chunk of today's stream on {} at {}.\n\n\
\"Nothing to criticize this week. The guy is absolutely dominating.\" \
The positive coverage should give confidence a boost in the locker room.",
\"Nothing to criticize this week. The guy is absolutely dominating the Rift: objective setups, draft execution, solo queue discipline, everything.\" \
The positive coverage should give confidence a boost in the team room.",
player_name, team_name
),
"be.msg.esportmaniacos.positive.subject0",
MessagePriority::Low,
),
_ => (
format!(
"Esportmaníacos: {} is carrying {} this split",
"Esportmaníacos: {} is carrying {} through every Draft",
player_name, team_name
),
format!(
"The Esportmaníacos tertulianos rarely agree on anything, but {} got a full pass today: \
\"Consistent, solid, no dips in form. One of the best players in the league right now. \
\"Consistent, solid, no dips in form. From draft reads to objective setups, one of the best players in the league right now. \
{} should do everything to keep him.\"\n\n\
Good for morale — and for the market value.",
player_name, team_name
Expand All @@ -301,27 +301,27 @@ pub fn build_media_story_from_narrative(
match variant {
0 => (
format!(
"Esportmaníacos goes after {} — panel shows no mercy",
"Draft Pressure on {} — panel shows no mercy",
player_name
),
format!(
"The Esportmaníacos panel did not hold back today on {}: \
\"The guy has completely disappeared. What happened to him this split? \
\"The guy has completely disappeared under lane pressure. What happened to him this split? \
{} can't keep relying on a player performing like this.\"\n\n\
This kind of coverage tends to hit morale hard. Worth having a word with the player.",
This kind of Rift coverage tends to hit morale hard. Worth having a word with the player after scrim review.",
player_name, team_name
),
"be.msg.esportmaniacos.negative.subject0",
MessagePriority::Normal,
),
_ => (
format!(
"Esportmaníacos questions {}'s consistency at {}",
"Pressure Watch: Esportmaníacos questions {}'s consistency at {}",
player_name, team_name
),
format!(
"Today's Esportmaníacos tertulianos session turned into a full breakdown of {}'s recent form. \
The verdict: \"Inconsistent. No regularity. Some days at top level, others completely invisible. \
The verdict: \"Inconsistent in lane and shaky in scrim patterns. No regularity. Some days at top level, others completely invisible on the Rift. \
{} deserves better output from a player in that role.\"\n\n\
Keep an eye on the player's morale.",
player_name, team_name
Expand Down
4 changes: 2 additions & 2 deletions src-tauri/crates/ofm_core/src/transfers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const PLAYER_INCOMING_OFFER_COOLDOWN_DAYS: i64 = 7;
const MANAGED_SQUAD_INCOMING_OFFER_COOLDOWN_DAYS: i64 = 14;
const TRANSFER_BUDGET_SELLING_REALLOCATION_PCT: i64 = 60;
const CONTRACT_RELEASE_PENALTY_PCT: i64 = 40;
const MAX_INCOMING_OFFERS_PER_DAY: usize = 1;
const MAX_INCOMING_OFFERS_PER_DAY: usize = 2;
const MAX_OFFERS_PER_TEAM_PER_WEEK: usize = 2;
const MAX_AI_FREE_AGENT_SIGNINGS_PER_DAY: usize = 2;
const MAX_AI_INTERCLUB_TRANSFERS_PER_DAY: usize = 1;
Expand Down Expand Up @@ -300,7 +300,7 @@ fn has_recent_incoming_offer(
return false;
};
let age_days = (current_date - offer_date).num_days();
age_days >= 0 && age_days < cooldown_days
age_days > 0 && age_days < cooldown_days
})
}

Expand Down
86 changes: 71 additions & 15 deletions src-tauri/crates/ofm_core/src/turn/post_match.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ use domain::stats::{
};
use log::debug;

fn compact_team_stats(stats: &engine::TeamStats) -> CompactTeamMatchStats {
fn compact_team_stats(stats: &engine::TeamStats, possession_pct: u8) -> CompactTeamMatchStats {
CompactTeamMatchStats {
possession_pct: 0,
possession_pct,
kills: stats.kills,
deaths: stats.deaths,
gold_earned: stats.gold_earned,
Expand Down Expand Up @@ -48,8 +48,11 @@ fn compact_match_report(report: &engine::MatchReport) -> CompactMatchReport {
CompactMatchReport {
total_minutes: report.total_minutes.into(),
game_duration_seconds: report.game_duration_seconds,
home_stats: compact_team_stats(&report.home_stats),
away_stats: compact_team_stats(&report.away_stats),
home_stats: compact_team_stats(&report.home_stats, report.home_possession.round() as u8),
away_stats: compact_team_stats(
&report.away_stats,
(100.0 - report.home_possession).round().clamp(0.0, 100.0) as u8,
),
events,
}
}
Expand Down Expand Up @@ -384,14 +387,38 @@ fn apply_player_stats(
) {
for player in game.players.iter_mut() {
if let Some(ps) = report.player_stats.get(&player.id) {
let minutes_played = if ps.duration_seconds > 0 {
ps.duration_seconds / 60
} else {
u32::from(ps.minutes_played)
};
let kills = if ps.kills > 0 {
ps.kills
} else {
report
.kill_feed
.iter()
.filter(|kill| kill.killer_id == player.id)
.count() as u16
};
player.stats.appearances += 1;
player.stats.kills += ps.kills as u32;
player.stats.kills += kills as u32;
player.stats.assists += ps.assists as u32;
player.stats.minutes_played += ps.duration_seconds / 60;

let match_rating = 6.0 + ((ps.kills + ps.assists) as f32 * 0.35)
- (ps.deaths as f32 * 0.25)
+ ((ps.damage_dealt as f32 / 10_000.0).min(1.5));
player.stats.minutes_played += minutes_played;
player.stats.shots += ps.shots as u32;
player.stats.shots_on_target += ps.shots_on_target as u32;
player.stats.passes_completed += ps.passes_completed as u32;
player.stats.passes_attempted += ps.passes_attempted as u32;
player.stats.tackles_won += ps.tackles_won as u32;
player.stats.interceptions += ps.interceptions as u32;

let match_rating = if ps.rating > 0.0 {
ps.rating
} else {
6.0 + ((kills + ps.assists) as f32 * 0.35)
- (ps.deaths as f32 * 0.25)
+ ((ps.damage_dealt as f32 / 10_000.0).min(1.5))
};
if player.stats.appearances == 1 {
player.stats.avg_rating = match_rating.clamp(0.0, 10.0);
} else {
Expand All @@ -400,8 +427,18 @@ fn apply_player_stats(
(player.stats.avg_rating * (n - 1.0) + match_rating.clamp(0.0, 10.0)) / n;
}

// In LoL, this logic doesn't apply - there are no "clean sheets" in LoL
// (the concept doesn't map - keeping for API compatibility)
if player.id.ends_with("_gk") {
let conceded = if player.team_id.as_deref() == Some(_home_team_id) {
report.away_wins
} else if player.team_id.as_deref() == Some(_away_team_id) {
report.home_wins
} else {
1
};
if minutes_played > 0 && conceded == 0 {
player.stats.clean_sheets += 1;
}
}
}
}
}
Expand Down Expand Up @@ -689,11 +726,20 @@ fn update_post_match_morale(

let mut individual_delta: i16 = 0;
if let Some(ps) = report.player_stats.get(&player.id) {
individual_delta += ps.kills as i16 * 3;
let kills = if ps.kills > 0 {
ps.kills
} else {
report
.kill_feed
.iter()
.filter(|kill| kill.killer_id == player.id)
.count() as u16
};
individual_delta += kills as i16 * 3;
individual_delta += ps.assists as i16 * 2;
if ps.deaths >= 5 {
individual_delta -= 3;
} else if ps.kills + ps.assists >= 6 {
} else if kills + ps.assists >= 6 {
individual_delta += 2;
}
}
Expand All @@ -717,11 +763,15 @@ fn update_team_form(

let home_result = if report.home_wins > report.away_wins {
"W"
} else if report.home_wins == report.away_wins {
"D"
} else {
"L"
};
let away_result = if report.away_wins > report.home_wins {
"W"
} else if report.away_wins == report.home_wins {
"D"
} else {
"L"
};
Expand Down Expand Up @@ -774,7 +824,13 @@ fn deplete_match_stamina(game: &mut Game, team_id: &str, report: &engine::MatchR
let minutes = report
.player_stats
.get(&player.id)
.map(|ps| (ps.duration_seconds / 60) as u8)
.map(|ps| {
if ps.duration_seconds > 0 {
(ps.duration_seconds / 60) as u8
} else {
ps.minutes_played as u8
}
})
.unwrap_or(0);
if minutes == 0 {
continue;
Expand Down
Loading
Loading