Pokemobs consist of an IPokemob providing capability attached to an entity.
- IPokemob inherits from:
IHasMobAIStates
- provides state tracking for combat, states of the mob's orders, etcIHasEntry
- tracking of a PokedexEntry and some breeding related methods
IHasMoves
- provides ability to use attacks, combat tracking, etcICanEvolve
- provies ability to change to different IPokemob typesIHasOwner
- provides tracking for an owner, as well as for sending messages to the owner, also tracks traded status and the pokecube the pokemob is in, as well as the controller for when riddenIOwnable
- ThutAPI for generic ownable things
IHasStats
- Tracks the pokemob'sAbility
,Nature
,PokeType
s, combat stats, etcIHungrymob
- Used for deciding on what the pokemob eats, and when it needs toIHasCommands
- Handles commands issued to the mob, usually from the ownerIMobColourable
- Handles RGBA tints for the mobIShearable
- Handles a generic "sheared" state, similar to vanilla sheepICopyMob
- Handles the ability for the pokemob to copy the appearance of another mob, mostly for transform and similar
IPokemob has the following for storing other information:
Mob
viaIHasEntry.getEntity()
- A vanilla mob associated with the Pokemob, if we are attached to aMob
, this will be what we are attached to, otherwise it will be aMob
that has the relevant values synced to/from the attached entity- PokedexEntry via
IHasEntry.getPokedexEntry()
- Contains most of the standard pokemon related information for the pokemob FormeHolder
- viaIPokemob.getCustomHolder()
- can be null, contains override information for the model/texture/types/etc for the individual IPokemob, separate from the standard PokedexEntry for it.PokemobMoveStats
- holder for a variety of combat related things, such asAbility
as modified in battle, the target for combat, the selected moves, etcStatModifiers
- Holds type information for the Pokemob, as well as modifications for stats for use in combat
Pokemob AI is done using a list of Behavior
(Vanilla type) objects just like standard mob AI. For the custom Pokecube classes however, they inherit from IAIRunnable
, and can be obtained via IPokemob.getTasks()
. Pokemob AI is initialised via pokecube.core.impl.capabilities.impl.PokemobAI
These are arranged into three groupings:
- IDLE - handles most non-combat AI
- COMBAT - handles combat movement, move use, enemy management, etc
- UTILITY - handles item gathering and out-of-combat move use
The InitAIEvent.Init
events are fired when the above lists are populated. This can be used to add to or modify the lists, if using the IAIRunnable
type of task. An example of this usage can be found via the Structure Builder Gimmick, which adds 2 tasks to the UTILITY
tasks list.
AI tasks can also be set to be automatically added by registering a ITaskAdder with TaskAdders.register. An example of this can be found via the Ant Nest Gimmick, where it adds a variety of sensors, memories and tasks. This method allows adding any arbitrary task type, rather than just IAIRunnable
.
There is then an additional list of Logic
objects, obtainable via IPokemob.getTickLogic()
These are presently as follows:
- LogicMountedControl - handles controls while ridden, effects like water breathing for dive, etc
- LogicInLiquid - any needed adjustments for when the pokemob is in water or lava
- LogicMovesUpdates - Ticks abilities while in combat, checks held item use, etc
- LogicInMaterials - Handles additional effects for when the pokemob is inside a material (damage from water, etc)
- LogicFloatFlySwim - deals with switching path finders/navigators for mobs which swim/fly/etc
- LogicMiscUpdate - ticks inventory items, validates AI states, checks for particle effects and animations for the mob
Combat consists of a Battle, where there are 2 lists of LivingEntity
which are listed as on opposing teams. A Battle ends when one of the teams is empty.
The PokemobMoveStats
for the IPokemob contains a tracker for both the current enemy LivingEntity
, and the index of the LivingEntity
in the list in the Battle. The owner of the IPokemob can change the target by setting that index.
Once a IPokemob has a target, it is up to the COMBAT
AI tasks to deal with what to do, and that is where the actual combat is done. The default behaviour is as follows:
UseAttacksTask
- Queues up a move use for the pokemob if the position/timing conditions are correctDodgeTask
- Attempts to dodge incoming movesLeapTask
- Attempts to jump at the enemy if a contact move is being executedCicleTask
- Keeps the pokemob close to the general center of where the fight started
A move gets excuted normally via the IHasMoves.executeMove
method, which takes parameters for a target entity, as well as a target location. The target location is more important in this case, but both parameters can be null, the following then occur:
- pokemob's selected move is determined, to ensure it is not on cooldown or disabled
- Next the attack cooldown for the pokemob is set (call to
IPokemob.setAttackCooldown
) ActualMoveUse.PreMoveStatus
event is fired onPokecubeAPI.MOVE_BUS
to determine if the test for status effects
- If cancelled, skips to step 4.
- Otherwise if the pokemob has flinched, is confused, or is infatuated, that is then checked next, and if the negative condition occurs, we exit here
- Finally
MovesUtils.useMove
is called to generate and start application of the move.
In MovesUtils.useMove
a MoveApplication
is made for the attack, and it is applied to targets based on the result of testing targets via MoveApplicationRegistry.getValidator
. The targets are selected either from the active Battle that the pokemob is in, or from just the target location and given user (for cases of out of combat move use). Before queueing the attack for application, a MoveUse.ActualMoveUse.Init
event is fired on the PokecubeAPI.MOVE_BUS
, and if it is cancelled, the attack will not be queued.
Queueing of the move consists of constructing a EntityMoveUse
, and marking it as ready to start applying next tick. Here is also where the CombatStates.USEDZMOVE
for the mob is recorded if nessisary. The queued moves get executed the next tick, and are sorted by priority there (including checks to VIT
stat), this however is not generally relevant. The EntityMoveUse
is then added to the level, and the move's hunger cost is applied to the pokemob, and a message about the move use is sent to those involved.
The EntityMoveUse
is a ThrowableProjectile
object and then works as follows:
- Initialises start/end/target/user information, and determines if it is a contact move or not.
- If contact/self, it will wrap around the user
- if AOE move, it is a 8m wide projectile
- if it has a custom size defined in the
MoveEntry
, it uses that. - otherwise is a 0.75m wide projectile
- Next the
EntityMoveUse
continues to move depending on if projectile or contact/self move, checking other targets it comes into contact with, as well as applying particle effects. - Once the
EntityMoveUse
has reached the age in ticks defined by the move'sIMoveAnimation
'sgetApplicationTick
, it will start applying to each hit target once. - Once the move has reached the end destination, or has timed out, or hit for a contact move, the
EntityMoveUse
is removed.
- If the move hits the target end destination, or is sufficiently close for a hit contact move, then it will attempt to apply any in-world effects, via the
MoveEntry.doWorldAction
Applying the move effects is done as follows for each LivingEntity
found during step 2 above:
- Check if the target is generally valid to hit, this checks that it was not already hit, if not valid, exit.
- Check if the move is allowed to hit others (
MoveEntry.canHitNonTarget
), and exit if target is not valid for that. - If we are not server side, exit.
- then initiate a battle if the target is not the original target
- mark the target as having been hurt by the user
- Call
MovesUtils.doAttack
to apply further - If the move is not AOE, and the target is not blocking, Apply the
MoveEntry.doWorldAction
and mark as finished, and remove theEntityMoveUse
.
MovesUtils.doAttack
validates the MoveEntry
, and then forwards the call to MoveEntry.applyMove
, which calls MoveApplicationRegistry.apply
MoveApplicationRegistry.apply
initialises the MoveApplication
for use via MoveApplicationRegistry.preApply
, where registered MOVE_MODIFIERS
are applied to the move, and the EFFECT_REGISTRY
is used to check of the move should have lasting effects. Next MoveApplication.preApply()
is called once to reset counters, and MoveApplication.apply()
is called for each time the move is expected to hit.
Some moves have effects which occur during MoveEntry.doWorldAction
, which works as follows:
MoveWorldAction.PreAction
is fired onPokecubeAPI.MOVE_BUS
, if cancelled, exitMoveWorldAction.OnAction
is fired onPokecubeAPI.MOVE_BUS
MoveWorldAction.PostAction
is fired onPokecubeAPI.MOVE_BUS
The default actions are applied during a low priority event listener in step 2.
MoveApplication.apply()
does the following:
- Fires the
DuringUse.Pre
event on thePokecubeAPI.MOVE_BUS
, if cancelled, exits. - Checks if the
MoveApplication
iscancelled
orfailed
.
- If not failed, checks the
PreApplyTests
for theMoveApplication
- If either of these failed, or there is no target for the move:
- Send messages to those involved in the battle
- Play sounds if we got here only because of no target
- Call our
OnMoveFail.onMoveFail
- Fires a
DuringUse.Post
event on thePokecubeAPI.MOVE_BUS
then exits
- Plays the move sounds if present
- Checks if we should
infatuate
, if so and the target is the pokemob, apply the infatuation - Constructs a
MoveApplication.Damage
via call to ourDamageApplier.applyDamage
- If the
efficiency
for theDamage
is <= 0 skip to step applyingPostMoveUse
- Apply stats effects/checks via
StatApplier.applyStats
- Apply status effects/checks via
StatusApplier.applyStatus
- Apply recoil via
RecoilApplier.applyRecoil
- Apply healing via
HealProvider.applyHealing
- Apply ongoing effects if present via
OngoingApplier.applyOngoingEffects
- Apply
PostMoveUse.applyPostMove
- Fires a
DuringUse.Post
event on thePokecubeAPI.MOVE_BUS
then exits
The various appliers mentioned above do the following for their default behaviour:
PreApplyTests
- checks status effectsSLP
,PAR
,FRZ
and returns false if they applyStatusApplier
- Applies any status effects the move should have to the targetStatApplier
- Applies stat modifications to the target, and sets theapplied_stat_effects
accordinglyDamageApplier
- Calculates damage to apply to the target, and applies it, also apply lightning bolt effects for appropriate moves, and makes psychic moves detonate creepers- Checks the
AccuracyProvider
to get theefficiency
for the move.
- Checks the
OngoingApplier
- no default actionsRecoilApplier
- Checks for damage/healing which occurs on move use, and applies to the user it based on the damage dealtHealProvider
- Checks for healing effects on the target, and applies it accordinglyPostMoveUse
- no default actionsOnMoveFail
- no default actions
These appliers can by replaced by registering appropriate classes in pokecube.mobs.moves.attacks
and including a @MoveProvider
annotation to declare which move it applies to. These will then replace the default appliers in the MoveApplicationRegistry.preApply
step above. There is some default parsing which occurs from loading in pokecube.api.data.moves.Moves
objects from json files, which attempts to generate appropriate effects for most moves, but for now custom logic needs to be implemented manually.
The DuringUse.Post
, DuringUse.Pre
and MoveWorldAction.OnAction
events have LOWEST
priority event handlers to do further processing. If they are cancelled before, they will not gety to the default processing.
-
DuringUse.Post
- ticks held items after move use
- calls
postMoveUse
for abilities of involved pokemobs
-
DuringUse.Pre
- ticks held items before move use
- Checks for
substitute
effects and fails the move accordingly - second step of held item use checks
- Check for false-swipe
- Check for block moves (like protect) via the
block-move
moves tag - Check for un-blockable moves via the
no-block-move
moves tag - Decrement counter for using block moves
-
MoveWorldAction.OnAction
- Looks up the move actions for the given attack.
- if no move actions, checks if it should have defaults applied for water, ice, electric or fire types
- if permissions are enabled, checks if owner has permission to use the action
- If in combat, applies
IMoveWorldEffect.applyInCombat
, otherwise applyIMoveWorldEffect.applyOutOfCombat
Abilities are implemented via classes which extend the Ability
class, they will be automatically loaded in from classes defined in pokecube.mobs.abilities
, and annotated with @AbilityProvider
. Addons can register other places to load from as done in pokecube.mobs.abilities.AbilityRegister::init
Abilities have the following methods:
startCombat
- called when a pokemob is added to a BattleendCombat
- called when the pokemob is removed from the BattlebeforeDamage
- called from theDamageApplier.applyDamage
to possibly modify damage dealtcanChange
- Ability dependant check for mega evolutiononAgress
- called when combat target is setpreMoveUse
- called during an event handler forDuringUse.Pre
eventspostMoveUse
- called during an event handler forDuringUse.Post
eventsonUpdate
- called each tick byLogicMovesUpdates
onRecall
- called when we recall to a pokecube, either via death or commanddestroy
- cleanup of ability for when mob is removed, only needed for abilities which register event listeners (likeDamp
)
Pokemobs use the genetics system provided by ThutAPI. Pokemobs by default track the following genes:
- AbilityGene
- ColourGene
- SpeciesGene
- IVsGene
- EVsGene
- MovesGene
- NatureGene
- ShinyGene
- SizeGene
- DynamaxGene
- TeraTypeGene
Pokemobs are loaded from data via files placed in database/pokemobs/pokedex_entries
, such as Abra. Normally these are generated by the data generation scripts, from data at PokeAPI. These are first loaded as either a JsonPokedexEntry or a list thereof. Then each JsonPokedexEntry
is converted to PokedexEntry.
If the value of stock
for the JsonPokedexEntry
is true
, then a minecraft EntityType
is registered for that entry. Otherwise it will be linked to the pokecube:missingno
entity type.
Pokemob animations are run by selecting the first state which occurs in the list provided by the IAnimated associated with the pokemob. These are populated in the orders listed below. If the mob is being ridden, then "ridden_<anim>" is added to the list before each of the strings listed below.
Once an animation is selected, it may then be randomised if sub-animations are registered via the xml files for that animation.
These are put in IAnimated.getChoices()
- "dead" - if the mob is dying
- lower case names of active LogicStates in order
- "floating" - if the mob is not moving horizontally, and has pose
FALL_FLYING
- "flying" - if the mob has pose, has pose
FALL_FLYING
- "floating" - if the mob is moving horizontally, and has pose
FALL_FLYING
- "sleeping" - if the mob has pose
SLEEPING
- "in_water" - if the mob is not moving horizontally, and has pose
SWIMMING
- "swimming" - if the mob has pose
SWIMMING
- "in_water" - if the mob is moving horizontally, and has pose
SWIMMING
- "guarding_sprinting" - if the mob is guarding and sprinting
- "sprinting" - if the mob is sprinting
- "guarding_walking" - if the mob is guarding and walking
- "walking" - if the mob is walking
- lower case names of active CombatStates in order
- lower case names of active GeneralStates in order
- "idle"
These are put in IAnimated.transientAnimations()
- "blink" - randomly approximately every 4 seconds
- "attack_<name>" - if the mob is EXECUTINGMOVE, where
<name>
is the name of the attack being used. - "attack_contact" - similar to above, but using a contact move
- "attack_ranged" - similar to above, but using a ranged move
- "attack_status" - similar to above, but using a status move
- "attack_other" - similar to above, but using a unknown move - this state shouldn't occur
- "battling" - if the mob is in a battle
The AnimationSelectionEvent is fired on the PokecubeAPI.POKEMOB_BUS
after the above lists are populated, this can be used to edit them accordingly.