| 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. |
How to Create a New Small Event
Overview
A Small Event is a random encounter that happens to players during travel reports. Each small event requires 6 files across 4 services.
Architecture: 6 Files Required
1. Packet Definition (Lib)
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 {
}
Key rules:
- Always use
@sendablePacket(PacketDirection.BACK_TO_FRONT)
- Fields use
! definite assignment assertion
- Extend
SmallEventAddSomething if you only need an amount field
- For pet info, include
petTypeId!: number, petSex!: SexTypeShort, petNickname!: string | undefined
2. Resource JSON (Core)
Path: 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)
3. Core Handler
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 Maps.isOnContinent(player);
},
executeSmallEvent: async (response, player): Promise<void> => {
response.push(makePacket(SmallEvent{Name}Packet, { }));
}
};
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:
- Give tokens:
await player.addTokens({ amount: 1, response, reason: NumberChangeReason.SMALL_EVENT }); await player.save();
- Give health:
await player.addHealth({ amount, response, reason: NumberChangeReason.SMALL_EVENT }); await player.save();
- Random number:
RandomUtils.rangedInt(SmallEventConstants.RANGE) or RandomUtils.randInt(min, maxExclusive)
- Random pick from array:
array[RandomUtils.randInt(0, array.length)]
- Player map info:
MapLinkDataController.instance.getById(player.mapLinkId) -> { startMap, endMap, tripDuration }
- Check location:
Maps.isOnContinent(player), Maps.isOnBoat(player), Maps.isOnPveIsland(player)
4. Icon (Lib)
Path: Lib/src/CrowniclesIcons.ts
Add an entry in the smallEvents object (around line 947+):
smallEvents: {
{name}: "🎯"
},
The smallEventId used in CrowniclesSmallEventEmbed must match this key.
5. Discord Handler
Path: Discord/src/packetHandlers/handlers/SmallEventsHandler.ts
Add a new @packetHandler method in the SmallEventsHandler class:
import { SmallEvent{Name}Packet } from "../../../../Lib/src/packets/smallEvents/SmallEvent{Name}Packet";
@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}",
getRandomSmallEventIntro(lng)
+ StringUtils.getRandomTranslation("smallEvents:{name}.stories", lng, { }),
interaction.user,
lng
)
]
});
}
Key imports available:
DiscordCache.getInteraction(context.discord!.interaction) — get Discord interaction
getRandomSmallEventIntro(lng) — random intro phrase
StringUtils.getRandomTranslation(key, lng, vars) — pick random translation
CrowniclesSmallEventEmbed(eventId, description, user, lng) — embed builder
PetUtils.petToShortString(lng, nickname, typeId, sex) — format pet name with emote
DisplayUtils.getPetIcon(typeId, sex) — get pet emoji
6. French Translations
Path: 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:
- Write at least 3-5 story variants
- Use
{{variable}} for template variables (e.g., {{pet}}, {{health, number}})
- Use
{emote:path} for emojis (e.g., {emote:unitValues.token}, {emote:unitValues.health})
- Bold important info with
**text**
- ONLY edit French translations — other languages are synced via Crowdin
- NEVER use direct speech (no dialogue with quotes)
- Stories are appended to a random intro phrase, so start with lowercase
Useful Data Models
Player
player.mapLinkId -> current travel link
player.level -> player level
player.petId -> player's pet ID (null if no pet)
player.health / player.getMaxHealth()
player.guildId -> guild ID (null if no guild)
PetExpedition
PetExpedition.findAll({ where: { mapLocationId, status: ExpeditionConstants.STATUS.IN_PROGRESS } }) — find active expeditions on a map
PetExpeditions.getActiveExpeditionForPlayer(playerId) — get player's active expedition
PetEntity
PetEntities.getById(id) -> PetEntity
petEntity.getBasicInfo() -> { petTypeId, petSex, petNickname }
petEntity.isFeisty() — check if pet is feisty
MapLink
MapLinkDataController.instance.getById(mapLinkId) -> { startMap, endMap, tripDuration }
Checklist Before Committing