Port events-mod to Factorio 2.0.77, declare SE/Krastorio 2 compatibility

Bumps the Factorio engine pin and typed-factorio (1.11.0 -> 3.36.0,
targeting 2.0.75) and fixes the resulting breaking API changes:
global -> storage, game.*_prototypes -> prototypes.*, game.table_to_json/
encode_string -> helpers.*, OnBuiltEntityEvent.created_entity -> .entity,
removed TileRead/ColorTable/MapPositionTable-style write-form aliases,
new CollisionMask shape, restructured settings-stage type imports, and
new defines.entity_status values (added to the Status enum, generated
schema, and EntityStatusRecord mapping). Also declares optional mod
dependencies on space-exploration and Krastorio2.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Lazzexus 2026-10-12 15:06:30 +02:00
parent 8281821075
commit 072d1a2845
20 changed files with 183 additions and 73 deletions

View file

@ -19,6 +19,10 @@ factorioMod {
modTitle.set("Kafkatorio Events")
modAuthor.set("adam@adamko.dev")
factorioCompatibility.set(libs.versions.factorio.map { it.substringBeforeLast(".") })
modDependencies.set(listOf(
"? space-exploration >= 0.7.0",
"? Krastorio2",
))
}
val licenseFile: RegularFile by rootProject.extra

View file

@ -13,7 +13,7 @@
"dependencies": {
"lua-types": "2.13.1",
"typescript-to-lua": "1.11.1",
"typed-factorio": "1.11.0",
"typed-factorio": "3.36.0",
"typescript": "4.9"
},
"license": "GPL-3.0-only",

View file

@ -1,4 +1,9 @@
---------------------------------------------------------------------------------------------------
Version: 0.1.0
Changes:
- Updated for Factorio 2.0.77 (storage API, prototypes.entity/prototypes.tile, new entity statuses)
- Declared optional compatibility with Space Exploration and Krastorio 2
---------------------------------------------------------------------------------------------------
Version: 0.0.1
Features:
- Kafkatorio

View file

@ -373,4 +373,28 @@ export enum FactorioEntityStatus {
TURNED_OFF_DURING_DAYTIME = "TURNED_OFF_DURING_DAYTIME",
NOT_CONNECTED_TO_RAIL = "NOT_CONNECTED_TO_RAIL",
CANT_DIVIDE_SEGMENTS = "CANT_DIVIDE_SEGMENTS",
GHOST = "GHOST",
BROKEN = "BROKEN",
FROZEN = "FROZEN",
PAUSED = "PAUSED",
NOT_CONNECTED_TO_HUB_OR_PAD = "NOT_CONNECTED_TO_HUB_OR_PAD",
NOT_ENOUGH_SPACE_IN_OUTPUT = "NOT_ENOUGH_SPACE_IN_OUTPUT",
FULL_BURNT_RESULT_OUTPUT = "FULL_BURNT_RESULT_OUTPUT",
WAITING_FOR_MORE_ITEMS = "WAITING_FOR_MORE_ITEMS",
WAITING_FOR_SPACE_IN_PLATFORM_HUB = "WAITING_FOR_SPACE_IN_PLATFORM_HUB",
THRUST_NOT_REQUIRED = "THRUST_NOT_REQUIRED",
ON_THE_WAY = "ON_THE_WAY",
WAITING_IN_ORBIT = "WAITING_IN_ORBIT",
WAITING_AT_STOP = "WAITING_AT_STOP",
WAITING_FOR_ROCKETS_TO_ARRIVE = "WAITING_FOR_ROCKETS_TO_ARRIVE",
NOT_ENOUGH_THRUST = "NOT_ENOUGH_THRUST",
DESTINATION_STOP_FULL = "DESTINATION_STOP_FULL",
NO_PATH = "NO_PATH",
NO_FILTER = "NO_FILTER",
NO_SPOT_SEEDABLE_BY_INPUTS = "NO_SPOT_SEEDABLE_BY_INPUTS",
WAITING_FOR_PLANTS_TO_GROW = "WAITING_FOR_PLANTS_TO_GROW",
COMPUTING_NAVIGATION = "COMPUTING_NAVIGATION",
PIPELINE_OVEREXTENDED = "PIPELINE_OVEREXTENDED",
RECIPE_NOT_RESEARCHED = "RECIPE_NOT_RESEARCHED",
RECIPE_IS_PARAMETER = "RECIPE_IS_PARAMETER",
}

View file

@ -3,7 +3,7 @@ import {KafkatorioKeyedPacketData} from "../types";
import PacketEmitter from "./PacketEmitter";
declare const global: {
declare const storage: {
/** Map the hash of the key, as a string, a cache entry */
eventUpdatesManagerCache: LuaTable<string, CacheEntry<KafkatorioKeyedPacketData>>,
}
@ -18,13 +18,13 @@ export class EventUpdatesManager {
init(force?: boolean) {
const isAnythingUndefined = global.eventUpdatesManagerCache == undefined
const isAnythingUndefined = storage.eventUpdatesManagerCache == undefined
log(`Initialising EventDataCache (force=${force}, isAnythingUndefined=${isAnythingUndefined})...`)
if (force == true || isAnythingUndefined) {
log("Initialising global.cache")
global.eventUpdatesManagerCache = new LuaTable<string, CacheEntry<KafkatorioKeyedPacketData>>()
log("Initialising storage.cache")
storage.eventUpdatesManagerCache = new LuaTable<string, CacheEntry<KafkatorioKeyedPacketData>>()
}
log(`Finished initialising EventDataCache`)
}
@ -103,12 +103,12 @@ export class EventUpdatesManager {
let countExpired = 0
let countTotal = 0
const expiredData: Array<KafkatorioKeyedPacketData> = []
for (const [key, entry] of global.eventUpdatesManagerCache) {
for (const [key, entry] of storage.eventUpdatesManagerCache) {
countTotal++
if (this.isExpired(entry)) {
countExpired++
expiredData.push(entry.packet)
global.eventUpdatesManagerCache.delete(key)
storage.eventUpdatesManagerCache.delete(key)
}
}
@ -146,10 +146,10 @@ export class EventUpdatesManager {
type: PacketType<PACKET>,
): CacheEntry<PACKET> | undefined {
const hash = EventUpdatesManager.hashKey(key)
if (!global.eventUpdatesManagerCache.has(hash)) {
if (!storage.eventUpdatesManagerCache.has(hash)) {
return undefined
}
const value: CacheEntry<any> = global.eventUpdatesManagerCache.get(hash)
const value: CacheEntry<any> = storage.eventUpdatesManagerCache.get(hash)
if (this.isEntryInstanceOf(value, type)) {
return value
} else {
@ -169,7 +169,7 @@ export class EventUpdatesManager {
}
const entry: CacheEntry<PACKET> = new CacheEntry<PACKET>(data)
const hash = EventUpdatesManager.hashKey(key)
global.eventUpdatesManagerCache.set(hash, entry)
storage.eventUpdatesManagerCache.set(hash, entry)
return entry
}
@ -206,7 +206,7 @@ export class EventUpdatesManager {
private static hashKey<PACKET extends KafkatorioKeyedPacketData>(
key: PacketKey<PACKET>,
): string {
return game.encode_string(game.table_to_json(key))!!
return helpers.encode_string(helpers.table_to_json(key))!!
}
}

View file

@ -2,18 +2,18 @@ export class EventDataQueueManager {
static init(force?: boolean) {
const isAnythingUndefined = global.store == undefined ||
global.size == undefined ||
global.head == undefined ||
global.tail == undefined
const isAnythingUndefined = storage.store == undefined ||
storage.size == undefined ||
storage.head == undefined ||
storage.tail == undefined
log(`Initialising EventDataQueueManager globals (force=${force}, isAnythingUndefined=${isAnythingUndefined})`)
if (force == true || isAnythingUndefined) {
global.store = {}
global.head = null
global.tail = null
global.size = 0
storage.store = {}
storage.head = null
storage.tail = null
storage.size = 0
}
log(`Finished initialising EventDataQueueManager`)
@ -21,38 +21,38 @@ export class EventDataQueueManager {
size(): uint | undefined {
return global.size
return storage.size
}
enqueue(key: string, event: EventData, weight: int = 1) {
if (key ! in global.store) {
if (key ! in storage.store) {
let node = new Node(key, weight)
if (global.head == null) {
global.head = node
if (storage.head == null) {
storage.head = node
}
if (global.tail != null) {
global.tail.next = node
if (storage.tail != null) {
storage.tail.next = node
}
global.tail = node
global.size++
storage.tail = node
storage.size++
}
global.store[key] = event
storage.store[key] = event
}
dequeue(): WeightedPacket | null {
if (global.head == null) {
if (storage.head == null) {
return null
} else {
let weight = global.head.weight
let storeKey = global.head.storeKey
global.head = global.head.next
global.size--
let weight = storage.head.weight
let storeKey = storage.head.storeKey
storage.head = storage.head.next
storage.size--
let storedPacket = global.store[storeKey]
global.store[storeKey] = null
let storedPacket = storage.store[storeKey]
storage.store[storeKey] = null
if (storedPacket == null) {
return null
@ -93,7 +93,7 @@ const EventDataQueue = new EventDataQueueManager()
export default EventDataQueue
declare const global: {
declare const storage: {
store: EventQueueStore
head: Node | null

View file

@ -9,7 +9,7 @@ export class KafkatorioPacketEmitter {
) {
KafkatorioPacketEmitter.emitPacket<KafkatorioPacket>({
data: data,
modVersion: global.MOD_VERSION,
modVersion: storage.MOD_VERSION,
tick: game.tick,
})
}
@ -19,21 +19,21 @@ export class KafkatorioPacketEmitter {
) {
KafkatorioPacketEmitter.emitPacket<KafkatorioPacket>({
data: data,
modVersion: global.MOD_VERSION,
modVersion: storage.MOD_VERSION,
tick: game.tick,
})
}
/** Emit a serialised KafkatorioPacket */
private static emitPacket<T extends KafkatorioPacket>(packet: T) {
const data = game.table_to_json(packet)
const data = helpers.table_to_json(packet)
if (data.trim().length <= 0) {
print(`[error] table_to_json returned empty string for packet:${packet}`)
return
}
const encodedData = game.encode_string(data)
const encodedData = helpers.encode_string(data)
if (encodedData == null || encodedData.trim().length <= 0) {
print(`[error] could not encode packet`)

View file

@ -29,7 +29,7 @@ export namespace Converters {
}
export function mapColour(color: ColorTable): Colour {
export function mapColour(color: Color): Colour {
return [
color.r ?? 0,
color.g ?? 0,
@ -42,8 +42,8 @@ export namespace Converters {
export function convertPlacedTiles(
placedTile: LuaTilePrototype,
oldTiles: OldTileAndPosition[],
): TileRead[] {
const converted: TileRead[] = []
): Tile[] {
const converted: Tile[] = []
for (const [, tile] of ipairs(oldTiles)) {
converted[converted.length] = {
position: {x: tile.position.x, y: tile.position.y},
@ -57,8 +57,8 @@ export namespace Converters {
export function convertRemovedTiles(
surface: LuaSurface,
oldTiles: OldTileAndPosition[],
): TileRead[] {
const converted: TileRead[] = []
): Tile[] {
const converted: Tile[] = []
for (const [, tile] of ipairs(oldTiles)) {
converted[converted.length] = surface.get_tile(tile.position.x, tile.position.y)
}
@ -68,7 +68,7 @@ export namespace Converters {
export function convertCollisionMaskToNames(cm: CollisionMask): string[] {
let masks: string[] = []
for (const [name] of pairs(cm)) {
for (const [name] of pairs(cm.layers)) {
masks.push(name)
}
return masks
@ -96,22 +96,22 @@ export namespace Converters {
}
export function tilePositionToChunkPosition(mapPosition: TilePositionTable): MapChunkPosition {
export function tilePositionToChunkPosition(mapPosition: TilePosition): MapChunkPosition {
return [floor(mapPosition.x / 32), floor(mapPosition.y / 32), ChunkSize.CHUNK_032]
}
export function mapPositionToChunkPosition(mapPosition: MapPositionTable): MapChunkPosition {
export function mapPositionToChunkPosition(mapPosition: MapPosition): MapChunkPosition {
return [floor(mapPosition.x / 32), floor(mapPosition.y / 32), ChunkSize.CHUNK_032]
}
export function entityPosition(position: MapPositionTable): MapEntityPosition {
export function entityPosition(position: MapPosition): MapEntityPosition {
return [position.x, position.y];
}
export function chunkPosition(position: ChunkPositionTable): MapChunkPosition {
export function chunkPosition(position: ChunkPosition): MapChunkPosition {
return [position.x, position.y, ChunkSize.CHUNK_032]
}
@ -218,6 +218,32 @@ export namespace Converters {
[ defines.entity_status.waiting_for_train ]: FactorioEntityStatus.WAITING_FOR_TRAIN,
[ defines.entity_status.waiting_to_launch_rocket ]: FactorioEntityStatus.WAITING_TO_LAUNCH_ROCKET,
[ defines.entity_status.working ]: FactorioEntityStatus.WORKING,
// Factorio 2.0 additions (Space Age / space platforms, agricultural towers, etc.)
[ defines.entity_status.ghost ]: FactorioEntityStatus.GHOST,
[ defines.entity_status.broken ]: FactorioEntityStatus.BROKEN,
[ defines.entity_status.frozen ]: FactorioEntityStatus.FROZEN,
[ defines.entity_status.paused ]: FactorioEntityStatus.PAUSED,
[ defines.entity_status.not_connected_to_hub_or_pad ]: FactorioEntityStatus.NOT_CONNECTED_TO_HUB_OR_PAD,
[ defines.entity_status.not_enough_space_in_output ]: FactorioEntityStatus.NOT_ENOUGH_SPACE_IN_OUTPUT,
[ defines.entity_status.full_burnt_result_output ]: FactorioEntityStatus.FULL_BURNT_RESULT_OUTPUT,
[ defines.entity_status.waiting_for_more_items ]: FactorioEntityStatus.WAITING_FOR_MORE_ITEMS,
[ defines.entity_status.waiting_for_space_in_platform_hub ]: FactorioEntityStatus.WAITING_FOR_SPACE_IN_PLATFORM_HUB,
[ defines.entity_status.thrust_not_required ]: FactorioEntityStatus.THRUST_NOT_REQUIRED,
[ defines.entity_status.on_the_way ]: FactorioEntityStatus.ON_THE_WAY,
[ defines.entity_status.waiting_in_orbit ]: FactorioEntityStatus.WAITING_IN_ORBIT,
[ defines.entity_status.waiting_at_stop ]: FactorioEntityStatus.WAITING_AT_STOP,
[ defines.entity_status.waiting_for_rockets_to_arrive ]: FactorioEntityStatus.WAITING_FOR_ROCKETS_TO_ARRIVE,
[ defines.entity_status.not_enough_thrust ]: FactorioEntityStatus.NOT_ENOUGH_THRUST,
[ defines.entity_status.destination_stop_full ]: FactorioEntityStatus.DESTINATION_STOP_FULL,
[ defines.entity_status.no_path ]: FactorioEntityStatus.NO_PATH,
[ defines.entity_status.no_filter ]: FactorioEntityStatus.NO_FILTER,
[ defines.entity_status.no_spot_seedable_by_inputs ]: FactorioEntityStatus.NO_SPOT_SEEDABLE_BY_INPUTS,
[ defines.entity_status.waiting_for_plants_to_grow ]: FactorioEntityStatus.WAITING_FOR_PLANTS_TO_GROW,
[ defines.entity_status.computing_navigation ]: FactorioEntityStatus.COMPUTING_NAVIGATION,
[ defines.entity_status.pipeline_overextended ]: FactorioEntityStatus.PIPELINE_OVEREXTENDED,
[ defines.entity_status.recipe_not_researched ]: FactorioEntityStatus.RECIPE_NOT_RESEARCHED,
[ defines.entity_status.recipe_is_parameter ]: FactorioEntityStatus.RECIPE_IS_PARAMETER,
//@formatter:on
}
}

View file

@ -64,7 +64,7 @@ export class EntityUpdatesHandler {
handleBuiltEntityEvent(
event: OnBuiltEntityEvent | OnRobotBuiltEntityEvent,
) {
EntityUpdatesHandler.throttleEntityUpdate(event, event.created_entity)
EntityUpdatesHandler.throttleEntityUpdate(event, event.entity)
}
@ -191,7 +191,7 @@ export class EntityUpdatesHandler {
/** Get all resources within a map-area */
private static getAreaResourceEntities(
surface: LuaSurface,
area: BoundingBoxTable,
area: BoundingBox,
): LuaEntity[] {
return surface.find_entities_filtered({
area: area,

View file

@ -40,7 +40,7 @@ class MapChunkUpdateHandler {
return
}
const tiles: TileRead[] = Converters.convertPlacedTiles(event.tile, event.tiles)
const tiles: Tile[] = Converters.convertPlacedTiles(event.tile, event.tiles)
const groupedTiles = MapChunkUpdateHandler.groupTiles(tiles)
for (const [chunkPos, tiles] of groupedTiles) {
@ -79,7 +79,7 @@ class MapChunkUpdateHandler {
return
}
const tiles: TileRead[] = Converters.convertRemovedTiles(surface, event.tiles)
const tiles: Tile[] = Converters.convertRemovedTiles(surface, event.tiles)
const groupedTiles = MapChunkUpdateHandler.groupTiles(tiles)
for (const [chunkPos, tiles] of groupedTiles) {
@ -152,7 +152,7 @@ class MapChunkUpdateHandler {
private static mapTilesUpdateDebounce(
surface: LuaSurface | undefined,
chunkPosition: MapChunkPosition,
tiles: TileRead[],
tiles: Tile[],
event: MapChunkUpdateEvent | MapTileChangeEvent,
updater?: MapChunkTileUpdater,
expirationDurationTicks?: uint,
@ -215,9 +215,9 @@ class MapChunkUpdateHandler {
}
private static groupTiles(tiles: TileRead[]): Map<MapChunkPosition, TileRead[]> {
private static groupTiles(tiles: Tile[]): Map<MapChunkPosition, Tile[]> {
const mapChunkPositionToTiles = new Map<MapChunkPosition, TileRead[]>()
const mapChunkPositionToTiles = new Map<MapChunkPosition, Tile[]>()
for (const tile of tiles) {
const chunkPosition = Converters.tilePositionToChunkPosition(tile.position)
if (!mapChunkPositionToTiles.has(chunkPosition)) {

View file

@ -33,7 +33,7 @@ function emit<T extends FactorioPrototype>(protosByType: PrototypesByType<T>) {
function getMapTilePrototypes(): PrototypesByType<FactorioPrototype.MapTile> {
const tiles: FactorioPrototype.MapTile[] = []
for (const [, tile] of game.tile_prototypes) {
for (const [, tile] of prototypes.tile) {
tiles[tiles.length] = {
type: FactorioPrototype.Type.MapTile,
@ -55,7 +55,7 @@ function getMapTilePrototypes(): PrototypesByType<FactorioPrototype.MapTile> {
function getEntityPrototypes(): PrototypesByType<FactorioPrototype.Entity> {
const protos: PrototypesByType<FactorioPrototype.Entity> = {}
for (const [, entity] of game.entity_prototypes) {
for (const [, entity] of prototypes.entity) {
const key: string = `${entity.group.name}/${entity.subgroup.name}/${entity.type}`
protos[key] ??= []

View file

@ -1,4 +1,4 @@
script.on_event(defines.events.on_runtime_mod_setting_changed, (e: OnRuntimeModSettingChangedEvent) => {
print(`Settings changed ${e.setting_type}, ${e.player_index}, ${e.setting}, ${e.mod_name}`)
print(`Settings changed ${e.setting_type}, ${e.player_index}, ${e.setting}`)
// KafkatorioSettings.loadSettings()
})

View file

@ -5,8 +5,8 @@ import {EventDataQueueManager} from "./emitting/EventDataQueue";
export function initGlobal(force: boolean = false) {
log(`Initialising Kafkatorio Global variables (force=${force})...`)
global.MOD_VERSION = script.active_mods[script.mod_name]
global.FACTORIO_VERSION = script.active_mods["base"]
storage.MOD_VERSION = script.active_mods[script.mod_name]
storage.FACTORIO_VERSION = script.active_mods["base"]
EventDataQueueManager.init(true)
EventUpdates.init(true)

View file

@ -1,4 +1,4 @@
declare const global: {
declare const storage: {
MOD_VERSION: string
FACTORIO_VERSION: string
}

View file

@ -1,4 +1,5 @@
import {Data, DoubleSettingDefinition} from "typed-factorio/settings/types"
import {SettingsData} from "factorio:common"
import {DoubleSettingDefinition} from "factorio:settings"
import {KafkatorioKeyedPacketData, KafkatorioKeyedPacketTypes} from "../types";
export class KafkatorioSettingsConfig {
@ -20,7 +21,7 @@ export class KafkatorioSettingsConfig {
}
public initialiseSettings(data: Data): void {
public initialiseSettings(data: SettingsData): void {
for (const [type,] of pairs(KafkatorioSettingsConfig.eventCacheExpirationDefaultSeconds)) {
data.extend(
[KafkatorioSettingsConfig.createDefaultCacheDuration(type)]

View file

@ -1,7 +1,7 @@
import {Data, Mods} from "typed-factorio/settings/types"
import {SettingsData, ActiveMods} from "factorio:common"
import KafkatorioSettings from "./main/settings/KafkatorioSettings";
declare const data: Data
declare const mods: Mods
declare const data: SettingsData
declare const mods: ActiveMods
KafkatorioSettings.initialiseSettings(data)

View file

@ -10,8 +10,7 @@
"types": [
"@typescript-to-lua/language-extensions",
"lua-types/5.2",
"typed-factorio/runtime",
"typed-factorio/strict-index-types"
"typed-factorio/runtime"
],
"plugins": [
{