SimFantasy¶
Simulator¶
Contains the primary classes and code required for running and managing simulations.
-
class
simfantasy.simulator.
Simulation
(combat_length: datetime.timedelta = None, log_level: int = None, log_event_filter: str = None, execute_time: datetime.timedelta = None, log_pushes: bool = None, log_pops: bool = None, iterations: int = None, log_action_attempts: bool = None) → None[source]¶ Bases:
object
Business logic for managing the simulated combat encounter and subsequent reporting.
Parameters: - combat_length (Optional[datetime.timedelta]) – Desired combat length. Default: 5 minutes.
- log_level (Optional[int]) – Minimum message level necessary to see logger output.
Default:
logging.INFO
. - log_event_filter (Optional[str]) – Pattern for filtering logging output to only matching class names. Default: None.
- execute_time (Optional[datetime.timedelta]) – Length of time to allow jobs to use “execute” actions. Default: 1 minute.
- log_pushes (Optional[bool]) – True to show events being placed on the queue. Default: True.
- log_pops (Optional[bool]) – True to show events being popped off the queue. Default: True.
- iterations (Optional[int]) – Number of encounters to simulate. Default: 100.
- log_action_attempts (Optional[bool]) – True to log actions attempted by
Actor
decision engines.
Variables: - actors (List[simfantasy.actor.Actor]) – Actors involved in the encounter.
- combat_length (datetime.timedelta) – Length of the encounter.
- current_iteration (int) – Current iteration index.
- current_time (datetime.datetime) – “In game” timestamp.
- events (queue.PriorityQueue[simfantasy.event.Event]) – Heapified list of upcoming events.
- execute_time (datetime.timedelta) – Length of time to allow jobs to use “execute” actions.
- iterations (int) – Number of encounters to simulate. Default: 100.
- log_action_attempts (bool) – True to log actions attempted by
Actor
decision engines. - log_event_filter (Optional[Pattern]) – Pattern for filtering logging output to only matching class names.
- log_pops (bool) – True to show events being popped off the queue. Default: True.
- log_pushes (bool) – True to show events being placed on the queue. Default: True.
- start_time (datetime.datetime) – Time that combat started.
-
in_execute
¶ Indicate whether the encounter is currently in an “execute” phase.
“Execute” phases are usually when an enemy falls below a certain health percentage, allowing actions such as
simfantasy.jobs.bard.MiserysEndAction
to be used.Returns: True if the encounter is in an execute phase, False otherwise. Return type: bool Examples
A fresh simulation that has just started a moment ago:
>>> sim = Simulation( ... combat_length=timedelta(seconds=60), ... execute_time=timedelta(seconds=30) ... ) >>> sim.start_time = sim.current_time = datetime.now() >>> print("Misery's End") if sim.in_execute else print('Heavy Shot') Heavy Shot
And now, if we adjust the start time to force us halfway into the execute phase:
>>> sim.current_time += timedelta(seconds=45) >>> print("Misery's End") if sim.in_execute else print('Heavy Shot') Misery's End
-
relative_timestamp
¶ Return a formatted string containing the number of seconds since the simulation began.
Returns: A string, with precision to the thousandths. Return type: str Examples
For a simulation that has been running for 5 minutes (300 seconds):
>>> sim.current_time = sim.start_time + timedelta(minutes=5) >>> sim.relative_timestamp '300.000'
And in another 30 seconds:
>>> sim.current_time += timedelta(seconds=30) >>> sim.relative_timestamp '330.000'
-
schedule
(event, delta: datetime.timedelta = None) → None[source]¶ Schedule an event to occur in the future.
Parameters: - event (simfantasy.event.Event) – The event to schedule.
- delta (Optional[datetime.timedelta]) – An optional amount of time to wait before the event should be executed. When delta is None, the event will be scheduled for the current timestamp, and executed after any preexisting events already scheduled for the current timestamp are finished.
Examples
>>> event.timestamp is None True >>> sim.schedule(event) >>> event.timestamp is sim.current_time True
-
unschedule
(event) → bool[source]¶ Unschedule an event, ensuring that it is not executed.
Does not “remove” the event. In actuality, flags the event itself as unscheduled to prevent having to resort the events list and subsequently recalculate the heap invariant.
Parameters: event (simfantasy.event.Event) – The event to unschedule. Returns: True if the event was unscheduled without issue. False if an error occurred, specifically a desync bug between the game clock and the event loop. Return type: bool Examples
Unscheduling an upcoming event:
>>> sim.schedule(event) >>> event.unscheduled False >>> sim.unschedule(event) True >>> event.unscheduled True
Rescheduling a previously-unscheduled event will reset its unscheduled flag:
>>> event.unscheduled True >>> sim.schedule(event) >>> event.unscheduled False
Unscheduling an event that has already occurred will fail:
>>> sim.schedule(event, timedelta(seconds=-30)) >>> event.timestamp < sim.current_time True >>> sim.unschedule(event) False >>> event.unscheduled False
Action¶
Abilities and weaponskills that can be performed by an actor.
-
class
simfantasy.action.
Action
(sim: simfantasy.simulator.Simulation, source: simfantasy.actor.Actor) → None[source]¶ Bases:
object
An ability that can be performed by an actor.
Parameters: - sim (simfantasy.simulator.Simulation) – The simulation where the action is performed.
- source (simfantasy.actor.Actor) – The actor that performed the action.
Variables: - animation (datetime.timedelta) – Length of the animation delay caused by the action. Default: 0.75 seconds.
- base_cast_time (datetime.timedelta) – Length of the action’s cast time. Default: Instant cast.
- base_recast_time (datetime.timedelta) – Length of time until the ability can be used again. Default: base GCD length, 2.5 seconds.
- can_recast_at (datetime.datetime) – Timestamp when the action can be performed again.
- cost (Tuple[simfantasy.enum.Resource, int]) – The resource type and amount needed to perform the action.
- guarantee_crit (bool) – Ensures the damage will be a critical hit. Default: False.
- hastened_by (simfantasy.enum.Attribute) – The attribute that contributes to lowering the cast/recast time of the action. Default: None.
- is_off_gcd (bool) – True for actions that are not bound to the GCD, False otherwise. Default: False.
- potency (int) – Potency of the action’s impact, i.e., damage healed or inflicted. Default: None.
- powered_by (Attribute) – The attribute that contributes to the total impact of the action. Default: None.
- shares_recast_with – (Union[simfantasy.action.Action, List[~simfantasy.action.Action]]): Action(s) that are on the same recast timer. Default: None.
- sim (simfantasy.simulator.Simulation) – The simulation where the action is performed.
- source (simfantasy.actor.Actor) – The actor that performed the action.
-
animation_execute_time
¶ Helper function to return whatever is longer, the action’s animation or cast time.
Returns: datetime.timedelta
-
name
¶ Name of the action.
Should be overridden with a friendlier name. Returns the class name by default.
Returns: Name of the action. Return type: string Examples
>>> class MyAction(Action): pass >>> MyAction(None, None).name 'MyAction' >>> class MyNamedAction(Action): ... @property ... def name(self): ... return 'CustomName' >>> MyNamedAction(None, None).name 'CustomName'
-
perform
()[source]¶ Perform the action.
Automatically schedules common action side effects when appropriate.
Examples
Actions that share a recast timer will have their recast times set:
>>> bloodletter.shares_recast_with = rain_of_death >>> rain_of_death.can_recast_at is None True >>> bloodletter.perform() >>> bloodletter.can_recast_at is not None True >>> bloodletter.can_recast_at == rain_of_death.can_recast_at True
-
ready
¶ Flag that indicates if the action can be performed or not.
Returns: True if the action can be performed, False otherwise. Return type: bool
-
schedule_aura_events
(target: simfantasy.actor.Actor, aura: simfantasy.aura.Aura)[source]¶ Schedule events to apply and remove an aura from a target.
For a new aura,
simfantasy.event.ApplyAuraEvent
andsimfantasy.event.ExpireAuraEvent
will be scheduled. If the aura already exists on the target, asimfantasy.event.RefreshAuraEvent
will be scheduled instead, which will subsequently adjust the timestamps of the existing events.Parameters: - target (simfantasy.actor.Actor) – The actor that will receive the aura.
- aura (simfantasy.aura.Aura) – The aura to apply to the actor.
Examples
A new aura will have its events scheduled for the first time:
>>> aura.application_event is None True >>> aura.expiration_event is None True >>> action.schedule_aura_events(actor, aura) >>> aura.application_event <simfantasy.event.ApplyAuraEvent ...> >>> aura.expiration_event <simfantasy.event.ExpireAuraEvent ...>
An existing aura will not receive new events, but instead will have its expiry event adjusted and rescheduled accordingly:
>>> original_expiry = aura.expiration_event >>> action.schedule_aura_events(actor, aura) >>> original_expiry is aura.expiration_event
-
schedule_damage_event
()[source]¶ Schedules an event to inflict damage.
Notes
The potency must not be None.
When the action is performed, i.e., after its
animation_execute_time
has passed, asimfantasy.event.DamageEvent
will inflict damage based on the amount defined in thepotency
.
-
schedule_resource_consumption
()[source]¶ Schedules an event to consume the resource needed to perform the action.
Notes
The cost must not be None.
When the action is performed, i.e., after its
animation_execute_time
has passed, asimfantasy.event.ResourceEvent
will occur that consumes the resource cost defined in thecost
.
-
set_recast_at
(delta: datetime.timedelta)[source]¶ Sets the timestamp when the action can be performed again.
Based on the given delta, sets the recast timestamp by adding it to the simulation’s current timestamp. If the action shares a recast with one or more other actions, those will have their recast timestamps set as well.
Parameters: delta (datetime.timedelta) – The amount of time that must pass to perform this action again. Examples
>>> action.set_recast_at(timedelta(seconds=30)) >>> action.can_recast_at == sim.current_time + timedelta(seconds=30) True
Actor¶
-
class
simfantasy.actor.
Actor
(sim: simfantasy.simulator.Simulation, race: simfantasy.enum.Race = None, level: int = None, target: typing.Union[simfantasy.actor.Actor, NoneType] = None, name: str = None, gear: typing.Dict[simfantasy.enum.Slot, simfantasy.equipment.Item] = None) → None[source]¶ Bases:
object
A participant in an encounter.
Warning
Although level is accepted as an argument, many of the formulae only work at level 70. This argument may be deprecated in the future, or at least restricted to max levels of each game version, i.e., 50, 60, 70 for A Realm Reborn, Heavensward, and Stormblood respectively, where it’s more likely that someone spent the time to figure out all the math.
Parameters: - sim (simfantasy.simulator.Simulation) – Pointer to the simulation that the actor is participating in.
- race (Optional[simfantasy.enum.Race]) – Race and clan of the actor.
- level (Optional[int]) – Level of the actor.
- target (Optional[simfantasy.actor.Actor]) – The enemy that the actor is targeting.
- name (Optional[str]) – Name of the actor.
- gear (Optional[Dict[Slot, Union[Item, Weapon]]]) – Collection of equipment that the actor is wearing.
Variables: - _target_data (Dict[Actor, TargetData) – Mapping of actors to any available target state data.
- animation_unlock_at (datetime.datetime) – Timestamp when the actor will be able to execute actions again without being inhibited by animation lockout.
- auras (List[simfantasy.aura.Aura]) – Auras, both friendly and hostile, that exist on the actor.
- gcd_unlock_at (datetime.datetime) – Timestamp when the actor will be able to execute GCD actions again without being inhibited by GCD lockout.
- gear (Optional[Dict[Slot, Union[Item, Weapon]]]) – Collection of equipment that the actor is wearing.
- job (simfantasy.enum.Job) – The actor’s job specialization.
- level (int) – Level of the actor.
- name (str) – Name of the actor.
- race (simfantasy.enum.Race) – Race and clan of the actor.
- resources (Dict[Resource, Tuple[int, int]]) – Mapping of resource type to a tuple containing the current amount and maximum capacity.
- sim (simfantasy.simulator.Simulation) – Pointer to the simulation that the actor is participating in.
- statistics (Dict[str, List[Dict[Any, Any]]]) – Collection of different event occurrences that are used for reporting and visualizations.
- stats (Dict[Attribute, int]) – Mapping of attribute type to amount.
- target (simfantasy.actor.Actor) – The enemy that the actor is targeting.
-
animation_up
¶ Determine if the actor is animation locked.
Many actions have an animation timing of 0.75s. This locks out the actor from performing multiple oGCD actions simultaneously. This lockout is tracked and can inhibit actions from being performed accordingly.
Returns: True if the actor is still animation locked, False otherwise. Return type: bool Examples
Consider an actor that has just performed some action, and is thus animation locked for 0.75s. During this period, the actor will be unable to perform actions that also have animation timings:
>>> actor.animation_unlock_at = sim.current_time + timedelta(seconds=0.75) >>> actor.animation_up False
However, once the simulation’s game clock advances past the animation lockout timestamp, the actor can once again perform actions:
>>> sim.current_time += timedelta(seconds=1) >>> actor.animation_up True
-
apply_gear_attribute_bonuses
()[source]¶ Apply stat bonuses gained from items and melds.
Examples
Consider the Kujakuo Kai bow for Bards:
>>> kujakuo_kai = Weapon(item_level=370, name='Kujakuo Kai', physical_damage=104, magic_damage=70, ... auto_attack=105.38, delay=3.04, ... stats={ ... Attribute.DEXTERITY: 347, ... Attribute.VITALITY: 380, ... Attribute.CRITICAL_HIT: 218, ... Attribute.DIRECT_HIT: 311, ... })
Equipping this item will add its stat bonuses to the actor:
>>> actor.equip_gear({Slot.WEAPON: kujakuo_kai}) >>> actor.apply_gear_attribute_bonuses() >>> actor.stats[Attribute.DEXTERITY] == 347 True
Bonuses from melded materia are also applied:
>>> savage_aim_vi = Materia(Attribute.CRITICAL_HIT, 40) >>> kujakuo_kai.melds = [savage_aim_vi, savage_aim_vi] >>> actor.stats = {} >>> actor.equip_gear({Slot.WEAPON: kujakuo_kai}) >>> actor.apply_gear_attribute_bonuses() >>> actor.stats[Attribute.CRITICAL_HIT] == 218 + 40 + 40 True
-
calculate_base_stats
() → typing.Dict[simfantasy.enum.Attribute, int][source]¶ Calculate and set base primary and secondary stats.
Base stats are determined by a combination of level, job and race/clan affiliation.
Returns: Mapping of attributes to amounts. Return type: Dict[Attribute, int]
-
calculate_resources
()[source]¶ Determine the resource levels for the actor.
In particular, sets the HP, MP and TP resource levels.
-
decide
() → typing.Iterable[source]¶ Given current simulation environment, decide what action should be performed, if any.
The “decision engine” for each actor is a generator function that yields the desired actions. This method should be constructed as a priority list, where more important actions are towards the top, and less important actions towards the bottom. A notable exception is for filler spells, i.e.
MeleeAttackAction
andShotAction
. Auto-attack actions don’t interfere with other skills and happen at regular intervals, so they can (and should) be safely placed at top priority.See also
Refer to
simfantasy.event.ActorReadyEvent.execute()
for clarification on what happens with actions yielded from the decision engine.Yields: Optional[simfantasy.action.Action] – An instance of an action that will attempt to be performed. If None is yielded, no further attempts to find a suitable action will be made until the actor is ready again.
-
equip_gear
(gear: typing.Dict[simfantasy.enum.Slot, simfantasy.equipment.Item])[source]¶ Equip items in the appropriate slots.
-
gcd_up
¶ Determine if the actor is GCD locked.
The global cooldown, or GCD, is a 2.5s lockout that prevents other GCD actions from being performed. Actions on the GCD are constrained by their “execute time”, or \(\max_{GCD, CastTime}\).
Returns: True if the actor is still GCD locked, False otherwise. Return type: bool Examples
Consider an actor that has just performed some action, and is thus gcd locked for 2.5s. During this period, the actor will be unable to perform actions that are also on the GCD:
>>> actor.gcd_unlock_at = sim.current_time + timedelta(seconds=2.5) >>> actor.gcd_up False
However, once the simulation’s game clock advances past the GCD lockout timestamp, the actor can once again perform GCD actions:
>>> sim.current_time += timedelta(seconds=3) >>> actor.gcd_up True
-
target_data
¶ Return target state data.
For new targets, or at least ones that the actor has never switched to before, there will not be any target data available. In that scenario, this property initializes a new instance of the target data class and returns it. If there is already target state data, it will be returned directly.
Returns: Contains all the target state data from the source actor to the target. Return type: simfantasy.actor.TargetData
Aura¶
-
class
simfantasy.aura.
Aura
(sim: simfantasy.simulator.Simulation, source: simfantasy.actor.Actor) → None[source]¶ Bases:
abc.ABC
A buff or debuff that can be applied to a target.
Variables: - application_event (simfantasy.event.ApplyAuraEvent) – Pointer to the scheduled event that will apply the aura to the target.
- duration (datetime.timedelta) – Initial duration of the aura.
- expiration_event (simfantasy.event.ExpireAuraEvent) – Pointer to the scheduled event that will remove the aura from the target.
- max_stacks (int) – The maximum number of stacks that the aura can accumulate.
- refresh_behavior (simfantasy.enum.RefreshBehavior) – Defines how the aura behaves when refreshed, i.e., what happens when reapplying an aura that already exists on the target.
- refresh_extension (datetime.timedelta) – For
simfantasy.enums.RefreshBehavior.EXTEND_TO_MAX
, this defines the amount of time that should be added to the aura’s current remaining time. - stacks (int) – The current number of stacks that the aura has accumulated. Should be less than or equal to max_stacks.
-
apply
(target) → None[source]¶ Apply the aura to the target.
Parameters: target (simfantasy.actor.Actor) – The target that the aura will be applied to. Examples
>>> class FakeActor: ... def __init__(self): ... self.auras = [] >>> actor = FakeActor() >>> aura = Aura() >>> aura in actor.auras False >>> aura.apply(actor) >>> aura in actor.auras True
-
expire
(target) → None[source]¶ Remove the aura from the target.
Warning
In the event that the aura does not exist on the target, the exception will be trapped, and error output will be shown.
Parameters: target (simfantasy.actor.Actor) – The target that the aura will be removed from.
-
name
¶ Return the name of the aura.
Examples
By default, shows the class name.
>>> class MyCustomAura(Aura): pass >>> aura = MyCustomAura() >>> aura.name 'MyCustomAura'
This property should be overwritten to provide a friendlier name, since it will be used for data visualization and reporting:
>>> class MyCustomAura(Aura): ... @property ... def name(self): ... return 'My Custom' >>> aura = MyCustomAura() >>> aura.name 'My Custom'
-
remains
¶ Return the length of time the aura will remain active on the target.
Examples
For auras with expiration events in the past, we interpret this to mean that they have already fallen off, and return zero:
>>> aura = Aura() >>> aura.remains == timedelta() True
On the other hand, if the expiration date is still forthcoming, we use its timestamp to determine the remaining time. Consider an aura that is due to expire in 30 seconds:
>>> sim = Simulation() >>> sim.current_time = datetime.now() >>> from simfantasy.event import ExpireAuraEvent >>> aura.expiration_event = ExpireAuraEvent(sim, None, aura) >>> aura.expiration_event.timestamp = sim.current_time + timedelta(seconds=30)
Obviously, the remaining time will be 30 seconds:
>>> aura.remains == timedelta(seconds=30) True
And if we move forward in time 10 seconds, we can expect the remaining time to decrease accordingly:
>>> sim.current_time += timedelta(seconds=10) >>> aura.remains == timedelta(seconds=20) True
-
class
simfantasy.aura.
TickingAura
(sim, source) → None[source]¶ Bases:
simfantasy.aura.Aura
An aura that ticks on the target, e.g., a damage-over-time spell.
Variables: tick_event (simfantasy.event.DotTickEvent) – Pointer to the event that will apply the next tick. -
ticks
¶ Return the base number of times that the aura will tick on the target.
Damage-over-time effects are synchronized to server tick events, so by default we assume that the number of ticks is \(\frac{duration}{3}\).
Returns: Number of ticks. Return type: int Examples
Consider a damage-over-time spell that has a base duration of 30 seconds:
>>> class MyDot(TickingAura): ... duration = timedelta(seconds=30) ... potency = 100
Since server ticks occur every 3 seconds, we can expect \(\frac{30}{3} = 10\) ticks:
>>> aura = MyDot() >>> aura.duration = timedelta(seconds=30) >>> aura.ticks 10
-
Equipment¶
-
class
simfantasy.equipment.
Item
(item_level: int, slot: simfantasy.enum.Slot, stats: typing.Dict[simfantasy.enum.Attribute, int], melds: typing.List[simfantasy.equipment.Materia] = None, name: str = None)[source]¶ Bases:
object
A piece of equipment that can be worn.
Parameters: - item_level (int) – Level of the item.
- slot (simfantasy.enum.Slot) – The slot where the item fits.
- stats (Dict[Attribute, int]) – Attributes added by the item.
- melds (Optional[List[Materia]]) – Materia affixed to the item.
- name (Optional[str]) – Name of the item, for convenience.
Variables:
-
class
simfantasy.equipment.
Materia
(attribute: simfantasy.enum.Attribute, bonus: int, name: str = None)[source]¶ Bases:
object
Provides a bonus to a specific stat.
Parameters: - attribute (simfantasy.enum.Attribute) – The attribute that will be modified.
- bonus (int) – Amount of the attribute added.
- name (Optional[str]) – Name of the materia, for convenience.
Variables: - attribute (simfantasy.enum.Attribute) – The attribute that will be modified.
- bonus (int) – Amount of the attribute added.
- name (Optional[str]) – Name of the item, for convenience.
-
class
simfantasy.equipment.
Weapon
(item_level: int, magic_damage: int, physical_damage: int, delay: float, auto_attack: float, stats: typing.Dict[simfantasy.enum.Attribute, int], melds: typing.List[simfantasy.equipment.Materia] = None, name: str = None)[source]¶ Bases:
simfantasy.equipment.Item
An Item that only fits in
SLOT_WEAPON
.Parameters: - item_level (int) – Level of the item.
- magic_damage (int) – Magic damage inflicted by the weapon. May be hidden for non-casters.
- physical_damage (int) – Physical damage inflicted by the weapon. May be hidden for casters.
- delay (float) – Weapon attack delay.
- auto_attack (float) – Auto attack value.
- stats (Dict[Attribute, int]) – Attributes added by the item.
- melds (Optional[List[Materia]]) – Materia affixed to the item.
- name (Optional[str]) – Name of the weapon, for convenience.
Variables: - auto_attack (float) – Auto attack value.
- delay (float) – Weapon attack delay.
- item_level (int) – Level of the item.
- magic_damage (int) – Magic damage inflicted by the weapon. May be hidden for non-casters.
- melds (Optional[List[Materia]]) – Materia affixed to the item.
- name (Optional[str]) – Name of the weapon, for convenience.
- physical_damage (int) – Physical damage inflicted by the weapon. May be hidden for casters.
- stats (Dict[Attribute, int]) – Attributes added by the item.
Events¶
-
class
simfantasy.event.
ActorReadyEvent
(sim: simfantasy.simulator.Simulation, actor)[source]¶ Bases:
simfantasy.event.Event
An event indicating that an
Actor
is ready to perform new actions.
-
class
simfantasy.event.
ApplyAuraEvent
(sim: simfantasy.simulator.Simulation, target, aura: simfantasy.aura.Aura)[source]¶ Bases:
simfantasy.event.AuraEvent
An event indicating that an aura should be added to an
Actor
.
-
class
simfantasy.event.
AuraEvent
(sim: simfantasy.simulator.Simulation, target, aura: simfantasy.aura.Aura)[source]¶ Bases:
simfantasy.event.Event
An event that deals with an “aura”, i.e., a buff or debuff that can be applied to an
Actor
.
-
class
simfantasy.event.
CombatEndEvent
(sim: simfantasy.simulator.Simulation)[source]¶ Bases:
simfantasy.event.Event
An event indicating that combat has ceased.
-
class
simfantasy.event.
Event
(sim: simfantasy.simulator.Simulation)[source]¶ Bases:
object
Emitted objects corresponding to in-game occurrences.
-
class
simfantasy.event.
ExpireAuraEvent
(sim: simfantasy.simulator.Simulation, target, aura: simfantasy.aura.Aura)[source]¶ Bases:
simfantasy.event.AuraEvent
An event indicating that an aura should be removed from an
Actor
.
Miscellany¶
Common math and constants¶
-
simfantasy.common_math.
get_base_resources_by_job
(job: simfantasy.enum.Job) → typing.Dict[simfantasy.enum.Resource, int][source]¶ Get base main stats by job.
Parameters: job – Job. Returns: Dictionary mapping Attribute
to integer bonus values.