with one click
create-small-event
// Step-by-step guide for creating a new Small Event (random encounter during player travel). Use this skill when asked to create, add, or implement a new small event in the Crownicles game.
// Step-by-step guide for creating a new Small Event (random encounter during player travel). Use this skill when asked to create, add, or implement a new small event in the Crownicles game.
| name | create-small-event |
| description | Step-by-step guide for creating a new Small Event (random encounter during player travel). Use this skill when asked to create, add, or implement a new small event in the Crownicles game. |
A Small Event is a random encounter that happens to players during travel reports. Each small event requires 6 files across 4 services.
Path: Lib/src/packets/smallEvents/SmallEvent{Name}Packet.ts
Create a packet class that extends SmallEventPacket (no extra fields) or SmallEventAddSomething (if you need an amount field).
import { SmallEventPacket } from "./SmallEventPacket";
import { PacketDirection, sendablePacket } from "../CrowniclesPacket";
@sendablePacket(PacketDirection.BACK_TO_FRONT)
export class SmallEvent{Name}Packet extends SmallEventPacket {
// Add any custom fields the Discord handler needs to display the event
// Use `!` for definite assignment: myField!: number;
}
Key rules:
@sendablePacket(PacketDirection.BACK_TO_FRONT)! definite assignment assertionSmallEventAddSomething if you only need an amount fieldpetTypeId!: number, petSex!: SexTypeShort, petNickname!: string | undefinedPath: Core/resources/smallEvents/{name}.json
{
"rarity": 1
}
Rarity is a weighted probability — higher = more frequent. Reference values:
1 = very rare (e.g., bigBad)3 = rare (e.g., winHealth)6 = uncommon (e.g., doNothing)8 = moderate (e.g., fightPet)40 = common (e.g., goToPVEIsland)50 = very common (e.g., haunted)Path: Core/src/core/smallEvents/{name}.ts
Must export smallEventFuncs: SmallEventFuncs with two functions:
import { SmallEventFuncs } from "../../data/SmallEvent";
import { makePacket } from "../../../../Lib/src/packets/CrowniclesPacket";
import { SmallEvent{Name}Packet } from "../../../../Lib/src/packets/smallEvents/SmallEvent{Name}Packet";
import { Maps } from "../maps/Maps";
export const smallEventFuncs: SmallEventFuncs = {
canBeExecuted: player => {
// Return true/false if this event can trigger for this player
// Common checks: Maps.isOnContinent(player), player.level >= X
return Maps.isOnContinent(player);
},
executeSmallEvent: async (response, player): Promise<void> => {
// Game logic here: modify player state, give rewards, etc.
// Always push a packet at the end
response.push(makePacket(SmallEvent{Name}Packet, { /* fields */ }));
}
};
Auto-discovery: Core automatically loads all files from dist/Core/src/core/smallEvents/*.js — no manual registration needed.
SmallEventFuncs interface:
type SmallEventFuncs = {
canBeExecuted: (player: Player) => boolean | Promise<boolean>;
executeSmallEvent: (response: CrowniclesPacket[], player: Player, context: PacketContext, testArgs?: string[]) => void | Promise<void>;
};
Common patterns:
await player.addTokens({ amount: 1, response, reason: NumberChangeReason.SMALL_EVENT }); await player.save();await player.addHealth({ amount, response, reason: NumberChangeReason.SMALL_EVENT }); await player.save();RandomUtils.rangedInt(SmallEventConstants.RANGE) or RandomUtils.randInt(min, maxExclusive)array[RandomUtils.randInt(0, array.length)]MapLinkDataController.instance.getById(player.mapLinkId) → { startMap, endMap, tripDuration }Maps.isOnContinent(player), Maps.isOnBoat(player), Maps.isOnPveIsland(player)Path: Lib/src/CrowniclesIcons.ts
Add an entry in the smallEvents object (around line 947+):
smallEvents: {
// ... existing entries ...
{name}: "🎯" // Pick an appropriate emoji
},
The smallEventId used in CrowniclesSmallEventEmbed must match this key.
Path: Discord/src/packetHandlers/handlers/SmallEventsHandler.ts
Add a new @packetHandler method in the SmallEventsHandler class:
// Add import at the top of the file
import { SmallEvent{Name}Packet } from "../../../../Lib/src/packets/smallEvents/SmallEvent{Name}Packet";
// Add method in SmallEventsHandler class
@packetHandler(SmallEvent{Name}Packet)
async smallEvent{Name}(context: PacketContext, packet: SmallEvent{Name}Packet): Promise<void> {
const interaction = DiscordCache.getInteraction(context.discord!.interaction);
const lng = interaction!.userLanguage;
await interaction?.editReply({
embeds: [
new CrowniclesSmallEventEmbed(
"{name}", // Must match CrowniclesIcons.smallEvents key
getRandomSmallEventIntro(lng)
+ StringUtils.getRandomTranslation("smallEvents:{name}.stories", lng, { /* template vars */ }),
interaction.user,
lng
)
]
});
}
Key imports available:
DiscordCache.getInteraction(context.discord!.interaction) — get Discord interactiongetRandomSmallEventIntro(lng) — random intro phraseStringUtils.getRandomTranslation(key, lng, vars) — pick random translationCrowniclesSmallEventEmbed(eventId, description, user, lng) — embed builderPetUtils.petToShortString(lng, nickname, typeId, sex) — format pet name with emoteDisplayUtils.getPetIcon(typeId, sex) — get pet emojiPath: Lang/fr/smallEvents.json
Add a new entry at the end of the JSON (before the closing }):
"{name}": {
"stories": [
"description narrative de ce qui se passe... **effet en gras** {emote:unitValues.health}",
"autre variante du texte...",
"encore une autre variante..."
]
}
Translation rules:
{{variable}} for template variables (e.g., {{pet}}, {{health, number}}){emote:path} for emojis (e.g., {emote:unitValues.token}, {emote:unitValues.health})**text**player.mapLinkId → current travel linkplayer.level → player levelplayer.petId → player's pet ID (null if no pet)player.health / player.getMaxHealth()player.guildId → guild ID (null if no guild)PetExpedition.findAll({ where: { mapLocationId, status: ExpeditionConstants.STATUS.IN_PROGRESS } }) — find active expeditions on a mapPetExpeditions.getActiveExpeditionForPlayer(playerId) — get player's active expeditionPetEntities.getById(id) → PetEntitypetEntity.getBasicInfo() → { petTypeId, petSex, petNickname }petEntity.isFeisty() — check if pet is feistyMapLinkDataController.instance.getById(mapLinkId) → { startMap, endMap, tripDuration }Lib/src/packets/smallEvents/ with @sendablePacket(PacketDirection.BACK_TO_FRONT)Core/resources/smallEvents/ with appropriate rarityCore/src/core/smallEvents/ exporting smallEventFuncsCrowniclesIcons.smallEvents in Lib/src/CrowniclesIcons.tsDiscord/src/packetHandlers/handlers/SmallEventsHandler.tsLang/fr/smallEvents.json with 3+ story variantsnpx tsc --noEmit in Core, Lib, and Discordpnpm test in Core