<commit_before>//! Gameplay logic that changes things

use crate::animations::{AnimState, Animations};
use crate::command::ActionOutcome;
use crate::components::{Brain, BrainState, MapMemory, Status};
use crate::effect::{Ability, Damage, Effect};
use crate::event::Event;
use crate::item::Slot;
use crate::location::Location;
use crate::mapsave;
use crate::query::Query;
use crate::spec;
use crate::terraform::Terraform;
use crate::volume::Volume;
use crate::world::{Ecs, Loadout};
use crate::Distribution;
use crate::{attack_damage, roll};
use calx::{Dir6, RngExt};
use calx_ecs::Entity;
use rand::seq::SliceRandom;
use rand::Rng;

/// World-mutating methods that are not exposed outside the crate.
pub trait Mutate: Query + Terraform + Sized + Animations {
    /// Advance world state after player input has been received.
    fn next_tick(&mut self);

    fn set_entity_location(&mut self, e: Entity, loc: Location);

    fn equip_item(&mut self, e: Entity, parent: Entity, slot: Slot);

    fn set_player(&mut self, player: Option<Entity>);

    /// Mark an entity as dead, but don't remove it from the system yet.
    fn kill_entity(&mut self, e: Entity);

    /// Remove an entity from the system.
    ///
    /// You generally do not want to call this directly. Mark the entity as dead and it will be
    /// removed at the end of the turn.
    fn remove_entity(&mut self, e: Entity);

    /// Compute field-of-view into entity's map memory.
    ///
    /// Does nothing for entities without a map memory component.
    fn do_fov(&mut self, e: Entity);

    /// Push an event to the event queue for this tick.
    fn push_event(&mut self, event: Event);

    /// Access the persistent random number generator.
    fn rng(&mut self) -> &mut crate::Rng;

    /// Mutable access to ecs
    fn ecs_mut(&mut self) -> &mut Ecs;

    /// Spawn an effect entity
    fn spawn_fx(&mut self, loc: Location, state: AnimState) -> Entity;

    /// Run AI for all autonomous mobs.
    fn ai_main(&mut self) {
        for npc in self.active_mobs() {
            self.heartbeat(npc);

            if !self.is_npc(npc) {
                continue;
            }
            if self.ticks_this_frame(npc) {
                self.run_ai_for(npc)
            }
        }
    }

    /// Run AI for one non-player-controlled creature.
    fn run_ai_for(&mut self, npc: Entity) {
        const WAKEUP_DISTANCE: i32 = 5;

        use crate::components::BrainState::*;
        let brain_state = self.brain_state(npc).expect("Running AI for non-mob");
        match brain_state {
            Asleep => {
                // XXX: Only treat player mob as potential hostile.
                // Can't model area conflict effects yet.
                if let (Some(loc), Some(player), Some(player_loc)) = (
                    self.location(npc),
                    self.player(),
                    self.player().map(|p| self.location(p)).unwrap_or(None),
                ) {
                    if self.player_sees(loc) {
                        // Okay, tricky spot. Player might be seeing mob across a portal, in
                        // which case we can't do naive distance check.
                        // This could have a helper method that finds chart distance to self in
                        // player's map memory.
                        //
                        // For now, let's just go with mobs past portals not waking up to
                        // player.

                        if loc.metric_distance(player_loc) <= WAKEUP_DISTANCE {
                            self.designate_enemy(npc, player);
                        }
                    }
                }
            }
            Hunting(target) => {
                if let (Some(my_loc), Some(target_loc)) =
                    (self.location(npc), self.location(target))
                {
                    if my_loc.metric_distance(target_loc) == 1 {
                        let _ = self.entity_melee(npc, my_loc.dir6_towards(target_loc).unwrap());
                    } else if let Some(move_dir) = self.pathing_dir_towards(npc, target_loc) {
                        let _ = self.entity_step(npc, move_dir);
                    }
                }
            }
            PlayerControl => {}
        }<commit_msg>NPC AI gets distracted every now and then<commit_after>//! Gameplay logic that changes things

use crate::animations::{AnimState, Animations};
use crate::command::ActionOutcome;
use crate::components::{Brain, BrainState, MapMemory, Status};
use crate::effect::{Ability, Damage, Effect};
use crate::event::Event;
use crate::item::Slot;
use crate::location::Location;
use crate::mapsave;
use crate::query::Query;
use crate::spec;
use crate::terraform::Terraform;
use crate::volume::Volume;
use crate::world::{Ecs, Loadout};
use crate::Distribution;
use crate::{attack_damage, roll};
use calx::{Dir6, RngExt};
use calx_ecs::Entity;
use rand::seq::SliceRandom;
use rand::Rng;

/// World-mutating methods that are not exposed outside the crate.
pub trait Mutate: Query + Terraform + Sized + Animations {
    /// Advance world state after player input has been received.
    fn next_tick(&mut self);

    fn set_entity_location(&mut self, e: Entity, loc: Location);

    fn equip_item(&mut self, e: Entity, parent: Entity, slot: Slot);

    fn set_player(&mut self, player: Option<Entity>);

    /// Mark an entity as dead, but don't remove it from the system yet.
    fn kill_entity(&mut self, e: Entity);

    /// Remove an entity from the system.
    ///
    /// You generally do not want to call this directly. Mark the entity as dead and it will be
    /// removed at the end of the turn.
    fn remove_entity(&mut self, e: Entity);

    /// Compute field-of-view into entity's map memory.
    ///
    /// Does nothing for entities without a map memory component.
    fn do_fov(&mut self, e: Entity);

    /// Push an event to the event queue for this tick.
    fn push_event(&mut self, event: Event);

    /// Access the persistent random number generator.
    fn rng(&mut self) -> &mut crate::Rng;

    /// Mutable access to ecs
    fn ecs_mut(&mut self) -> &mut Ecs;

    /// Spawn an effect entity
    fn spawn_fx(&mut self, loc: Location, state: AnimState) -> Entity;

    /// Run AI for all autonomous mobs.
    fn ai_main(&mut self) {
        for npc in self.active_mobs() {
            self.heartbeat(npc);

            if !self.is_npc(npc) {
                continue;
            }
            if self.ticks_this_frame(npc) {
                self.run_ai_for(npc)
            }
        }
    }

    /// Run AI for one non-player-controlled creature.
    fn run_ai_for(&mut self, npc: Entity) {
        const WAKEUP_DISTANCE: i32 = 5;

        use crate::components::BrainState::*;
        let brain_state = self.brain_state(npc).expect("Running AI for non-mob");
        match brain_state {
            Asleep => {
                // XXX: Only treat player mob as potential hostile.
                // Can't model area conflict effects yet.
                if let (Some(loc), Some(player), Some(player_loc)) = (
                    self.location(npc),
                    self.player(),
                    self.player().map(|p| self.location(p)).unwrap_or(None),
                ) {
                    if self.player_sees(loc) {
                        // Okay, tricky spot. Player might be seeing mob across a portal, in
                        // which case we can't do naive distance check.
                        // This could have a helper method that finds chart distance to self in
                        // player's map memory.
                        //
                        // For now, let's just go with mobs past portals not waking up to
                        // player.

                        if loc.metric_distance(player_loc) <= WAKEUP_DISTANCE {
                            self.designate_enemy(npc, player);
                        }
                    }
                }
            }
            Hunting(target) => {
                if self.rng().one_chance_in(12) {
                    self.ai_drift(npc);
                } else {
                    self.ai_hunt(npc, target);
                }
            }
            PlayerControl => {}
        }
    }

    /// Approach and attack target entity.
    fn ai_hunt(&mut self, npc: Entity, target: Entity) {
        if let (Some(my_loc), Some(target_loc)) = (self.location(npc), self.location(target)) {
            if my_loc.metric_distance(target_loc) == 1 {
                let _ = self.entity_melee(npc, my_loc.dir6_towards(target_loc).unwrap());
            } else {
                if let Some(move_dir) = self.pathing_dir_towards(npc, target_loc) {
                    let _ = self.entity_step(npc, move_dir);
                } else {
                    self.ai_drift(npc);
                }
            }
        }
    }

    /// Wander around aimlessly
    fn ai_drift(&mut self, npc: Entity) {
        let dirs = Dir6::permuted_dirs(self.rng());
        for &dir in &dirs {
            if self.entity_step(npc, dir).is_some() {
                return;
            }
        }
{'fraction_non_alphanumeric': 0.1114263887373131, 'fraction_numerical': 0.0016370184437411329, 'mean_word_length': 1.8170919151552414, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2929466', 'n_tokens_mistral': 2679, 'n_tokens_neox': 2548, 'n_words': 790}