logic_constructor 0.1.0

Move combat and ability logic out of code and into HOCON config — designers tweak damage, healing, and targeting in a text file; the engine parses it into typed actions and runs them against your entities.
Documentation
# Logic Constructor

Logic Constructor lets you move combat and ability logic out of code and into HOCON config. Designers tweak damage, healing, and targeting in a text file; the engine parses it into typed actions and runs them against your entities — no recompile, no glue code per ability.


## Example

This is a simplified example which uses data from `tests/fixture.rs`.

```rust
fn main() {
    // 1. Configuration and parsing
    let hocon = r#"
    light-attack = [
        { DealDamage: 15 }
        { lca: { Heal: 4 }, collision: "Self" }
    ]
    "#;

    let parsed: Value = Config::parse_str(hocon, None).unwrap();
    let light_attack_config_value = parsed.as_object().unwrap().get("light-attack").unwrap();

    let light_attack: LcActionConfig<LcGameEntity> =
        parse_lc_action_config(light_attack_config_value, &parse_game_effect).unwrap();

    // 2. Running
    let player = Player { hp: Rc::new(RefCell::new(100.0)) };
    let enemy  = Enemy  { hp: Rc::new(RefCell::new(100.0)) };

    run_lca(&light_attack, &player.clone().into(), &enemy.clone().into());

    assert_eq!(*enemy.hp.borrow(), 85.0);    // OTHER hit enemy: -15
    assert_eq!(*player.hp.borrow(), 104.0);  // SELF healed source: +4
}
```


## Configuration & Boilerplate

- Define entities which would qualify for running logic constructor actions on.
- Define logic constructor actions.

### Defining entities

#### Configuration

First we are going to define our entities structures.

```rust
pub struct Player {
    hp: Rc<RefCell<f32>>,
}

pub struct Enemy {
    hp: Rc<RefCell<f32>>,
}

// ... and more
```

Second we should define an enum which acts as a "registry" for all possible entities
we will be using for logic constructor (it's best practice to call it `LcGameEntity`).

```rust
pub enum LcGameEntity {
    Player(Player),
    Enemy(Enemy),
    // ... and more
}
```

#### Boilerplate

Now we have to tie it down to the logic constructor engine.

We have to implement `LcEntityType` for our game entities enum (also make sure that 
`LcEntityTypeId` would be a unique value per entity type).

```rust
impl LcEntityType for LcGameEntity {
    fn type_id(&self) -> LcEntityTypeId {
        match self {
            LcGameEntity::Player(_) => 1,
            LcGameEntity::Enemy(_) => 2,
            // ... and more 
        }
    }
}
```

Define helper `From` implementations for every type, so it would be easier to pass
in entities into `run_lca` function.

```rust
impl From<Player> for LcEntity<LcGameEntity> {
    fn from(value: Player) -> Self {
        LcEntity {
            game_entity: LcGameEntity::Player(value),
        }
    }
}

impl From<Enemy> for LcEntity<LcGameEntity> {
    fn from(value: Enemy) -> {
        LcEntity {
            game_entity: LcGameEntity::Enemy(value),
        }
    }
}

// ... and more
```

Additionally for ease of use we can define helper methods on our `LcGameEntity`,
so it would be easier to implement logic constructor actions, like so.

```rust
impl LcGameEntity {
    pub fn health(&self) ->Rc<RefCell<f32>> {
        match self {
            LcGameEntity::Player(e) => e.hp.clone(),
            LcGameEntity::Enemy(e) => e.hp.clone(),
            // ... and more
        }
    }
}
```

### Defining actions

#### Boilerplate

Before configuring our actions, first we will define some boilerplate so it would be easier
later on to define our actions.

First we will define our `Action` trait which combines with our `LcGameEntity`.

```rust
pub trait LcGameAction: LcAction<LcGameEntity> {
    fn apply(&self, source: LcGameEntity, target: LcGameEntity);
}
```

Then since we will always need to implement `LcAction<LcGameEntity>` per action, we can write
simple macro to help use out reduce boilerplate per action definition.

```rust
macro_rules! impl_lc_game_action {
    ($t:ty) => {
        impl LcAction<LcGameEntity> for $t {
            fn apply(&self, source: &LcEntity<LcGameEntity>, target: &LcEntity<LcGameEntity>) {
                <Self as LcGameAction>::apply(
                    self,
                    source.game_entity.clone(),
                    target.game_entity.clone(),
                )
            }
            fn clone_box(&self) -> Box<dyn LcAction<LcGameEntity>> {
                Box::new(self.clone())
            }
        }
    }
}
```

#### Configuration

Now when we have our boilerplate in place, we can define our actions, we will define a struct
which will hold information for a specific action and implement a trait, which we will tell
how to apply an action from source to target.

```rust
// Define action and it's data.
pub struct DealDamage {
    pub amount: f32,
}

// Define how it applies from source onto target.
impl_lc_game_action!(DealDamage);
impl LcGameAction for DealDamage {
    fn apply(&self, _source: LcGameEntity, target: LcGameEntity) {
        *target.health.borrow_mut() -= self.amount;
    }
}

pub struct Heal {
    pub amount: f32,
}

impl_lc_game_action!(Heal);
impl LcGameAction for Heal {
    fn apply(&self, _source: LcGameEntity, target: LcGameEntity) {
        *target.health.borrow_mut() += self.amount;
    }
}

// ... and more
```

#### Parsing

We of course also need to parse the data from hocon configuration, since we are defining which 
actions are available inside the config.

The parsers can be defined in many ways, but it's goal is to take hoccon `Value` type and parse
it into our `LcGameAction`. Later on when parsing from hocon config we will be injecting this
parser function into `run_lca`.

```rust
pub fn parse_game_action(value: &Value) -> Result<Box<dyn LcAction<LcGameEntity>>, String> {
    let obj = value
        .as_object()
        .ok_or_else(|| format!("expected object, got {:?}", value))?;
    if obj.len() != 1 {
        return Err(format!("expected single key, got {}", obj.len()));
    }
    let (name, inner) = obj.iter().next().unwrap();
    let amount = match inner {
        Value::Number(n) => n
            .as_f64()
            .ok_or_else(|| format!("{} expects a number", name))? as f32,
        _ => return Err(format!("{} expects a number", name)),
    }
    match name.as_str() {
        "DealDamage" => Ok(Box::new(DealDamage { amount })),
        "Heal" => Ok(Box::new(Heal { amount })),
        // ... and more
        other => Err(format!("unknown action: {}", other)),
    }
}
```

## Roadmap

This is only scrathing the surface of what is the real potential of logic constructor
in time will be adding way more features as I'll be using this library to develop my own games.

### Features

- Buffs - have actions performed when applied or removed. Another action would be to apply a buff.
- Stats & Math - inside configs instead of typing out raw number for damage you will be able to write
`10 * STR` or something similar.
- Periodic Actions - an action which is applied every x times, or per custom rules. For example 
while player has slow buff it ticks damage every 2 seconds.
- Effects - They will be able to last over time like a moment speed reduction while buff is applied.