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

Introduce Typed JS using JSDoc and begin battle engine rework #57

Merged
merged 39 commits into from
Oct 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
cb50f50
types setup
ewei068 Sep 27, 2024
7e5cb5e
fix index errors ig
ewei068 Sep 27, 2024
e335766
more config
ewei068 Sep 27, 2024
afe8f76
eslint
ewei068 Sep 27, 2024
a1b92f6
part 1
ewei068 Sep 27, 2024
0ba90f0
Merge branch 'main' of https://github.com/ewei068/pokestar into typed-js
ewei068 Sep 28, 2024
a772678
fix battle tower and typedef trainer
ewei068 Sep 28, 2024
5a10132
fix types for battle commands
ewei068 Sep 28, 2024
ea4b817
fix types for pokemon commands + guild type
ewei068 Sep 29, 2024
06fd96f
type other commands
ewei068 Sep 29, 2024
8c5ce24
remove items from backpack category config
ewei068 Oct 1, 2024
66eeba8
make enums or something
ewei068 Oct 3, 2024
70a4a55
rename pokemon types
ewei068 Oct 4, 2024
237fd5b
begin battle refactor
ewei068 Oct 4, 2024
d473eb6
begin new move service
ewei068 Oct 4, 2024
4989fd0
use new move service
ewei068 Oct 4, 2024
3a52b97
basic move migration
ewei068 Oct 4, 2024
9c63c29
init effects service
ewei068 Oct 4, 2024
e75f088
fix eslint
ewei068 Oct 5, 2024
9b5012c
use getEffect
ewei068 Oct 5, 2024
324b9e8
effect add and remove
ewei068 Oct 5, 2024
2c6c5fa
migrate one effect
ewei068 Oct 5, 2024
53346f0
migrate shield effect
ewei068 Oct 5, 2024
117a256
improve move registry
ewei068 Oct 6, 2024
e3b5747
remove remaining move config usages
ewei068 Oct 6, 2024
da3ffa5
improve effect registry
ewei068 Oct 6, 2024
5eea95f
small rename
ewei068 Oct 6, 2024
5879c98
effect registry refactor
ewei068 Oct 7, 2024
ad0906c
move battle data
ewei068 Oct 7, 2024
d6b90e3
more refactor
ewei068 Oct 7, 2024
450ae61
more moving around
ewei068 Oct 7, 2024
67984bd
some more changes
ewei068 Oct 7, 2024
00ca095
enum rename
ewei068 Oct 7, 2024
6a3af13
begin event listener migration
ewei068 Oct 7, 2024
d930c8d
conditional event improvements
ewei068 Oct 7, 2024
0e57f02
begin event emit migration
ewei068 Oct 7, 2024
d11e95b
create ability registry
ewei068 Oct 12, 2024
23e1860
ability class and types
ewei068 Oct 12, 2024
9eecbe7
ability migration
ewei068 Oct 12, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import pluginJs from "@eslint/js";

export default [
// mimic ESLintRC-style extends
...compat.extends("airbnb", "prettier"),
...compat.extends("airbnb", "prettier", "plugin:jsdoc/recommended"),
{
files: ["**/*.js"],
languageOptions: {
Expand All @@ -34,6 +34,12 @@ export default [
"guard-for-in": "off", // TODO: enable but make better lol
"no-continue": "off", // doesn't seem to work properly
"no-await-in-loop": "off", // TODO: use promise.all
"jsdoc/no-undefined-types": "off", // doesn't seem to work with TS types
"jsdoc/require-param-description": "off", // TODO: maybe add descriptions
"jsdoc/require-returns-description": "off", // TODO: maybe add descriptions
"jsdoc/require-property-description": "off", // TODO: maybe add descriptions
"jsdoc/valid-types": "off", // doesn't seem to work with TS types
"jsdoc/require-returns": "off",
},
},
pluginJs.configs.recommended,
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,9 +31,12 @@
},
"devDependencies": {
"@eslint/js": "^9.11.0",
"@types/node": "^22.7.3",
"eslint": "^8.57.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^9.1.0",
"globals": "^15.9.0"
"eslint-plugin-jsdoc": "^50.3.0",
"globals": "^15.9.0",
"typescript": "^5.6.2"
}
}
77 changes: 77 additions & 0 deletions src/battle/data/abilities.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/* eslint-disable no-param-reassign */
const { abilityIdEnum, battleEventEnum } = require("../../enums/battleEnums");
const { logger } = require("../../log");
const { getIsActivePokemonCallback } = require("../engine/eventConditions");

/**
* @template T
*/
class Ability {
/**
* @param {object} param0
* @param {AbilityIdEnum} param0.id
* @param {string} param0.name
* @param {string} param0.description
* @param {AbilityAddCallback<T>} param0.abilityAdd
* @param {AbilityRemoveCallback<T>} param0.abilityRemove
*/
constructor({ id, name, description, abilityAdd, abilityRemove }) {
this.id = id;
this.name = name;
this.description = description;
this.abilityAdd = abilityAdd;
this.abilityRemove = abilityRemove;
this.isLegacyAbility = false;
}

// eslint-disable-next-line jsdoc/require-returns-check
/**
* @param {BattlePokemon} pokemon
* @returns {{ abilityId: AbilityIdEnum, data: T, applied: boolean }=}
*/
getAbilityInstance(pokemon) {
const abilityInstance = pokemon.ability;
if (abilityInstance?.abilityId !== this.id) {
logger.error(
`Ability ${this.id} not found on Pokemon ${pokemon.id} ${pokemon.name}. Real Ability ID: ${abilityInstance?.abilityId}`
);
return;
}

return /** @type {any} */ (abilityInstance);
}
}

const abilitiesToRegister = Object.freeze({
[abilityIdEnum.REGENERATOR]: new Ability({
id: abilityIdEnum.REGENERATOR,
name: "Regenerator",
description: "After the user's turn, heal 15% of its max HP.",
abilityAdd({ battle, target }) {
return {
listenerId: battle.registerListenerFunction({
eventName: battleEventEnum.TURN_END,
callback: ({ activePokemon }) => {
// heal 15% of max hp
battle.addToLog(
`${activePokemon.name}'s Regenerator restores its health!`
);
const healAmount = Math.floor(activePokemon.maxHp * 0.15);
activePokemon.giveHeal(healAmount, activePokemon, {
type: "regenerator",
});
},
conditionCallback: getIsActivePokemonCallback(battle, target),
}),
};
},
abilityRemove({ battle, properties }) {
battle.unregisterListener(properties.listenerId);
},
}),
});

module.exports = {
Ability,
abilitiesToRegister,
};
102 changes: 102 additions & 0 deletions src/battle/data/abilityRegistry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// eslint-disable-next-line no-unused-vars
const { abilityIdEnum } = require("../../enums/battleEnums"); // TODO: remove after testing
const { logger } = require("../../log");
// eslint-disable-next-line no-unused-vars
const types = require("../../../types");

const allAbilities = {};

/**
* @param {Record<AbilityIdEnum, Ability<any>>} abilities
*/
const registerAbilities = (abilities) => {
let abilitiesRegistered = 0;
Object.entries(abilities).forEach(([abilityId, ability]) => {
allAbilities[abilityId] = ability;
abilitiesRegistered += 1;
});
logger.info(`Registered ${abilitiesRegistered} abilities.`);
};

/**
* @param {Record<AbilityIdEnum, object>} abilityConfig
*/
const registerLegacyAbilities = (abilityConfig) => {
let abilitiesRegistered = 0;
Object.entries(abilityConfig).forEach(([abilityId, ability]) => {
if (allAbilities[abilityId]) {
logger.warn(
`Ability ${abilityId} ${allAbilities[abilityId].name} already exists. Continuing...`
);
return;
}
allAbilities[abilityId] = {
...ability,
isLegacyAbility: true,
};
abilitiesRegistered += 1;
});
logger.info(`Registered ${abilitiesRegistered} legacy abilities.`);
};

/**
* @template {AbilityIdEnum} K
* @param {K} abilityId
* @returns {K extends keyof RegisteredAbilities ? RegisteredAbilities[K] : Ability<any>?}
*/
const getAbility = (abilityId) =>
// @ts-ignore
allAbilities[abilityId];

/**
* @param {object} param0
* @param {Record<string, any>=} param0.fieldFilter
* @param {Function=} param0.customFilter
* @returns {types.PartialRecord<AbilityIdEnum, Ability<any>>}
*/
const getAbilities = ({ fieldFilter, customFilter }) => {
if (customFilter) {
return Object.entries(allAbilities).reduce((acc, [abilityId, ability]) => {
if (customFilter(ability)) {
acc[abilityId] = ability;
}
return acc;
}, {});
}

if (fieldFilter) {
return Object.entries(allAbilities).reduce((acc, [abilityId, ability]) => {
for (const [field, value] of Object.entries(fieldFilter)) {
if (ability[field] !== value) {
return acc;
}
}
acc[abilityId] = ability;
return acc;
}, {});
}

return {
...allAbilities,
};
};

/**
* @param {object} param0
* @param {Record<string, any>=} param0.fieldFilter
* @param {Function=} param0.customFilter
* @returns {AbilityIdEnum[]}
*/
const getAbilityIds = ({ fieldFilter, customFilter }) => {
const abilities = getAbilities({ fieldFilter, customFilter });
// @ts-ignore
return Object.keys(abilities);
};

module.exports = {
registerAbilities,
registerLegacyAbilities,
getAbility,
getAbilities,
getAbilityIds,
};
101 changes: 101 additions & 0 deletions src/battle/data/effectRegistry.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// eslint-disable-next-line no-unused-vars
const { effectIdEnum } = require("../../enums/battleEnums"); // TODO: remove after testing
const { logger } = require("../../log");
// eslint-disable-next-line no-unused-vars
const types = require("../../../types");

const allEffects = {};

/**
* @param {Record<EffectIdEnum, Effect<any, any>>} effects
*/
const registerEffects = (effects) => {
let effectsRegistered = 0;
Object.entries(effects).forEach(([effectId, effect]) => {
allEffects[effectId] = effect;
effectsRegistered += 1;
});
logger.info(`Registered ${effectsRegistered} effects.`);
};

/**
* @param {Record<EffectIdEnum, object>} effectConfig
*/
const registerLegacyEffects = (effectConfig) => {
let effectsRegistered = 0;
Object.entries(effectConfig).forEach(([effectId, effect]) => {
if (allEffects[effectId]) {
logger.warn(
`Effect ${effectId} ${allEffects[effectId].name} already exists. Continuing...`
);
return;
}
allEffects[effectId] = {
...effect,
isLegacyEffect: true,
};
effectsRegistered += 1;
});
logger.info(`Registered ${effectsRegistered} legacy effects.`);
};

/**
* @template {EffectIdEnum} K
* @param {K} effectId
* @returns {K extends keyof RegisteredEffects ? RegisteredEffects[K] : Effect<any, any>}
*/
const getEffect = (effectId) =>
// @ts-ignore
allEffects[effectId];

/**
* @param {object} param0
* @param {Record<string, any>=} param0.fieldFilter
* @param {Function=} param0.customFilter
* @returns {types.PartialRecord<EffectIdEnum, Effect<any, any>>}
*/
const getEffects = ({ fieldFilter, customFilter }) => {
if (customFilter) {
return Object.entries(allEffects).reduce((acc, [effectId, effect]) => {
if (customFilter(effect)) {
acc[effectId] = effect;
}
return acc;
}, {});
}

if (fieldFilter) {
return Object.entries(allEffects).reduce((acc, [effectId, effect]) => {
for (const [field, value] of Object.entries(fieldFilter)) {
if (effect[field] !== value) {
return acc;
}
}
acc[effectId] = effect;
return acc;
}, {});
}

return {
...allEffects,
};
};
/**
* @param {object} param0
* @param {Record<string, any>=} param0.fieldFilter
* @param {Function=} param0.customFilter
* @returns {EffectIdEnum[]}
*/
const getEffectIds = ({ fieldFilter, customFilter }) => {
const effects = getEffects({ fieldFilter, customFilter });
// @ts-ignore
return Object.keys(effects);
};

module.exports = {
registerEffects,
registerLegacyEffects,
getEffect,
getEffects,
getEffectIds,
};
Loading
Loading