Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Define a top-level public-friendly API for our damage calculation logic #84

Open
ajhyndman opened this issue Apr 19, 2017 · 7 comments
Open

Comments

@ajhyndman
Copy link
Owner

I'm thinking it will just export a single function, probably:

type HeroInstance = // ...

type Result = // ...

declare module 'fire-emblem-heroes-calculator' {
  declare calculateResult(initiator: HeroInstance, defender: HeroInstance): Result;
};
@ajhyndman
Copy link
Owner Author

ajhyndman commented Apr 19, 2017

Hmm, this API also needs support for buffs.

We could pass optional "supporting hero" instances, as further arguments, and then just scrape allies for any and all support skills and assume all of them apply. I kind of like that solution. If you don't want a hero to be granting his support skill, you probably shouldn't have the skill equipped, or the supporting hero selected.

Maybe that's too specific to our app though. Maybe this calculator module should just accept raw numbers. It could be proving-grounds's responsibility to translate the supporting units' skills into buff values.

@ajhyndman
Copy link
Owner Author

ajhyndman commented Apr 19, 2017

Proposal A

type MergeLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
type Rarity = 1 | 2 | 3 | 4 | 5;
type Stat = 'hp' | 'atk' | 'spd' | 'def' | 'res';

type HeroConfiguration = {
  +bane?: ?Stat;
  +boon?: ?Stat;
  +mergeLevel?: ?MergeLevel;
  +name: string;
  +rarity: Rarity;
  +skills: {
    +WEAPON?: ?string;
    +ASSIST?: ?string;
    +SPECIAL?: ?string;
    +PASSIVE_A?: ?string;
    +PASSIVE_B?: ?string;
    +PASSIVE_C?: ?string;
    +SEAL?: ?string;
  };
};

type HeroState = {
  +currentHp?: ?number;
  +currentCooldown?: ?number;
  +buffs?: {
    [stat: Stat]?: {
      persistent?: ?number;
      proximity?: ?number;
    };
  };
};

type Turn = {
  damage: number;
  specialDamage: number;
  numAttacks: number;
  // this can be extended in the future to include, e.g.
  // - life gain
  // - pre-battle damage
  // - post-battle damage
};

declare const calculateResult: ({
  attacker: {
    config: HeroConfiguration;
    state: HeroState;
  };
  defender: {
    config: HeroConfiguration;
    state: HeroState;
  };
}): {
  attacker: {
    state: HeroState;
    turn: Turn;
  };
  defender: {
    state: HeroState;
    turn: Turn;
  };
};

@ajhyndman
Copy link
Owner Author

ajhyndman commented Apr 19, 2017

The only thing I don't like about the above is that it doesn't preserve enough information in its output to allow us to write a log like this:

image

@AlmostMatt
Copy link
Collaborator

AlmostMatt commented Apr 20, 2017

I'll think about this in more detail later, but I suspect that anyone who uses our combat logic will want a bunch of relevant helpers:

GetStat / stats. GetDefaultInstance, getSpecialCooldown, getInheritableSkills

@ajhyndman
Copy link
Owner Author

ajhyndman commented May 7, 2017

I'm kind of liking the idea of a timeline-based approach. It's noteworthy that the below proposal is not sufficient information to draw the number of attacks that what would be permissible by speed, if the unit didn't die first (which is what the game's UI does).

Proposal B

type MergeLevel = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
type Rarity = 1 | 2 | 3 | 4 | 5;
type Stat = 'hp' | 'atk' | 'spd' | 'def' | 'res';

type HeroBuild = {
  +bane?: ?Stat;
  +boon?: ?Stat;
  +mergeLevel?: ?MergeLevel;
  +name: string;
  +rarity: Rarity;
  +skills: {
    +WEAPON?: ?string;
    +ASSIST?: ?string;
    +SPECIAL?: ?string;
    +PASSIVE_A?: ?string;
    +PASSIVE_B?: ?string;
    +PASSIVE_C?: ?string;
    +SEAL?: ?string;
  };
};

type HeroState = {
  +currentHp?: ?number;
  +currentCooldown?: ?number;
  +buffs?: {
    [stat: Stat]?: {
      persistent?: ?number;
      proximity?: ?number;
    };
  };
};

type HeroInstance = {
  build: HeroBuild;
  state: HeroState;
};

type Multiplier = {
  type: 'ADVANTAGE' | 'EFFECTIVE';
  value: number;
  // source: ???
};

type Event = {
  type: 'ATTACK',
  target: 'ATTACKER' | 'DEFENDER',
  multipliers: Array<Multiplier>
  damage: number;
} | {
  type: 'PRE-BATTLE',
  target: 'ATTACKER' | 'DEFENDER',
  damage: number;
  source: string; // skill name
} | {
  type: 'POST-BATTLE',
  target: 'ATTACKER' | 'DEFENDER',
  damage: number;
  source: string; // skill name
} | {
  type: 'HEAL',
  target: 'ATTACKER' | 'DEFENDER',
  amount: number;
  source: string; // skill name
};

declare const calculateResult: ({
  attacker: HeroInstance;
  defender: HeroInstance;
}): {
  attackerState: HeroState;
  defenderState: HeroState;
  timeline: Array<Event>;
};

@AlmostMatt
Copy link
Collaborator

AlmostMatt commented May 7, 2017

Specials can also multiple damage (aegis/glimmer), add a flat amount of damage (bonfire), reduce def/res, or do something unique (miracle).
These could be attack modifiers or they could be SPECIAL events with extraDamage, damageMitigated

Buffs and Debuffs would be another type of action (seal def, etc)

This should be a type
{
config: HeroConfiguration;
state: HeroState;
}

buffs/debuffs are persistent, I don't really want to call a spur a buff.
Also it's possible to have a buff and debuff to the same stat at once, so a debuff is not the same as a negative buff.

@ajhyndman
Copy link
Owner Author

I updated Proposal B to change Configuration to Build and named the collection of both to Instance.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

2 participants