massive refactor to export entities (buildings and resources)
- minor version bumps - new Factorio version - use both prototype name and type to identify a prototype - kxs-ts-gen: use BrandTyping, but exclude some identifiers - kxs-ts-gen: manually create zip dist, so it can run after the project compiles - mod: use typed-factorio strict types - mod: refactor event handlers to be a little more consistent and object-oriented
This commit is contained in:
parent
88982d818a
commit
90dc520d22
62 changed files with 2629 additions and 713 deletions
|
|
@ -1,14 +1,16 @@
|
|||
[mod-setting-name]
|
||||
|
||||
kafkatorio:cache-expiration-seconds_kafkatorio-packet-keyed-PlayerUpdate=Player update rate (seconds)
|
||||
kafkatorio:cache-expiration-seconds_kafkatorio-packet-keyed-MapChunkUpdate=Map update rate (seconds)
|
||||
kafkatorio:cache-expiration-seconds_kafkatorio-packet-keyed-MapChunkTileUpdate=Map tile update rate (seconds)
|
||||
kafkatorio:cache-expiration-seconds_kafkatorio-packet-keyed-MapChunkEntityUpdate=Entity update rate (seconds)
|
||||
kafkatorio:cache-expiration-seconds_kafkatorio-packet-keyed-EntityUpdate=Entity update rate (seconds)
|
||||
|
||||
|
||||
[mod-setting-description]
|
||||
|
||||
kafkatorio:cache-expiration-seconds_kafkatorio-packet-keyed-PlayerUpdate=How frequently Player updates are sent to the Kafkatorio server, in seconds. Lower means updates are sent more frequently, which may cause network congestion or reduce performance. The default (0.25 seconds) is probably fine!
|
||||
kafkatorio:cache-expiration-seconds_kafkatorio-packet-keyed-MapChunkUpdate=How frequently Map updates are sent to the Kafkatorio server, in seconds. Lower means updates are sent more frequently, which may cause network congestion or reduce performance. The default (30 seconds) is probably fine!
|
||||
kafkatorio:cache-expiration-seconds_kafkatorio-packet-keyed-MapChunkTileUpdate=How frequently Map updates are sent to the Kafkatorio server, in seconds. Lower means updates are sent more frequently, which may cause network congestion or reduce performance. The default (30 seconds) is probably fine!
|
||||
kafkatorio:cache-expiration-seconds_kafkatorio-packet-keyed-MapChunkEntityUpdate=How frequently entity updates are sent to the Kafkatorio server, in seconds. Lower means updates are sent more frequently, which may cause network congestion or reduce performance. The default (30 seconds) is probably fine!
|
||||
kafkatorio:cache-expiration-seconds_kafkatorio-packet-keyed-EntityUpdate=How frequently Entity updates are sent to the Kafkatorio server, in seconds. Lower means updates are sent more frequently, which may cause network congestion or reduce performance. The default (5 seconds) is probably fine!
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import {initGlobal} from "./main/global-init";
|
||||
import {emitPrototypes} from "./main/events/prototypeUpdates";
|
||||
|
||||
require("./main/events/registerHandlers")
|
||||
require("./main/events/registerEventListeners")
|
||||
require("./main/events/mapChunkUpdates")
|
||||
require("./main/events/playerUpdates")
|
||||
require("./main/events/entityUpdates")
|
||||
|
|
|
|||
|
|
@ -14,7 +14,8 @@ export type KafkatorioPacketData =
|
|||
| KafkatorioPacketData.ConsoleCommandUpdate
|
||||
| KafkatorioPacketData.EntityUpdate
|
||||
| KafkatorioPacketData.Error
|
||||
| KafkatorioPacketData.MapChunkUpdate
|
||||
| KafkatorioPacketData.MapChunkEntityUpdate
|
||||
| KafkatorioPacketData.MapChunkTileUpdate
|
||||
| KafkatorioPacketData.PlayerUpdate
|
||||
| KafkatorioPacketData.PrototypesUpdate
|
||||
| KafkatorioPacketData.SurfaceUpdate;
|
||||
|
|
@ -27,7 +28,8 @@ export namespace KafkatorioPacketData {
|
|||
PrototypesUpdate = "kafkatorio.packet.instant.PrototypesUpdate",
|
||||
SurfaceUpdate = "kafkatorio.packet.instant.SurfaceUpdate",
|
||||
EntityUpdate = "kafkatorio.packet.keyed.EntityUpdate",
|
||||
MapChunkUpdate = "kafkatorio.packet.keyed.MapChunkUpdate",
|
||||
MapChunkEntityUpdate = "kafkatorio.packet.keyed.MapChunkEntityUpdate",
|
||||
MapChunkTileUpdate = "kafkatorio.packet.keyed.MapChunkTileUpdate",
|
||||
PlayerUpdate = "kafkatorio.packet.keyed.PlayerUpdate",
|
||||
Error = "kafkatorio.packet.KafkatorioPacketData.Error",
|
||||
}
|
||||
|
|
@ -77,12 +79,18 @@ export namespace KafkatorioPacketData {
|
|||
lastUser?: UInt | null;
|
||||
localisedDescription?: string | null;
|
||||
localisedName?: string | null;
|
||||
prototype?: PrototypeName | null;
|
||||
}
|
||||
|
||||
export interface MapChunkUpdate {
|
||||
type: KafkatorioPacketData.Type.MapChunkUpdate;
|
||||
key: MapChunkUpdateKey;
|
||||
export interface MapChunkEntityUpdate {
|
||||
type: KafkatorioPacketData.Type.MapChunkEntityUpdate;
|
||||
key: MapChunkEntityUpdateKey;
|
||||
events?: { [key: EventName]: Tick[] } | null;
|
||||
distinctEntities?: { [key: string]: FactorioEntityData } | null;
|
||||
}
|
||||
|
||||
export interface MapChunkTileUpdate {
|
||||
type: KafkatorioPacketData.Type.MapChunkTileUpdate;
|
||||
key: MapChunkTileUpdateKey;
|
||||
events?: { [key: EventName]: Tick[] } | null;
|
||||
player?: PlayerIndex | null;
|
||||
robot?: EntityIdentifiersData | null;
|
||||
|
|
@ -132,16 +140,11 @@ export interface ConfigurationUpdateGameData {
|
|||
newVersion?: string | null;
|
||||
}
|
||||
|
||||
export type PlayerIndex = UInt;
|
||||
|
||||
export type SurfaceIndex = UInt;
|
||||
|
||||
export type Double = double;
|
||||
|
||||
export interface EntityUpdateKey {
|
||||
unitNumber: UnitNumber;
|
||||
name: string;
|
||||
protoType: string;
|
||||
protoId: PrototypeId;
|
||||
}
|
||||
|
||||
export type MapEntityPosition = [
|
||||
|
|
@ -153,32 +156,33 @@ export type UByte = uint8;
|
|||
|
||||
export type Float = float;
|
||||
|
||||
export type PrototypeName = string;
|
||||
export interface MapChunkEntityUpdateKey {
|
||||
protoId: PrototypeId;
|
||||
surfaceIndex: SurfaceIndex;
|
||||
chunkPosition: MapChunkPosition;
|
||||
}
|
||||
|
||||
export interface MapChunkUpdateKey {
|
||||
export interface MapChunkTileUpdateKey {
|
||||
chunkPosition: MapChunkPosition;
|
||||
surfaceIndex: SurfaceIndex;
|
||||
}
|
||||
|
||||
export interface EntityIdentifiersData {
|
||||
unitNumber?: UnitNumber | null;
|
||||
name: string;
|
||||
protoType: string;
|
||||
protoId: PrototypeId;
|
||||
}
|
||||
|
||||
export type ForceIndex = UInt;
|
||||
export type ForceIndex = UInt & { __ForceIndex__: void };
|
||||
|
||||
export interface MapTileDictionary {
|
||||
tilesXY: { [key: string]: { [key: string]: PrototypeKey } };
|
||||
protos: { [key: PrototypeName]: PrototypeKey };
|
||||
protos: { [key: PrototypeId]: PrototypeKey };
|
||||
}
|
||||
|
||||
export interface PlayerUpdateKey {
|
||||
index: PlayerIndex;
|
||||
}
|
||||
|
||||
export type UnitNumber = UInt;
|
||||
|
||||
export type Colour = [
|
||||
red: Float,
|
||||
green: Float,
|
||||
|
|
@ -206,7 +210,7 @@ export namespace FactorioPrototype {
|
|||
|
||||
export interface Entity {
|
||||
type: FactorioPrototype.Type.Entity;
|
||||
name: PrototypeName;
|
||||
protoId: PrototypeId;
|
||||
mapColour: Colour;
|
||||
protoType: string;
|
||||
objectName: string;
|
||||
|
|
@ -220,7 +224,7 @@ export namespace FactorioPrototype {
|
|||
|
||||
export interface MapTile {
|
||||
type: FactorioPrototype.Type.MapTile;
|
||||
name: PrototypeName;
|
||||
protoId: PrototypeId;
|
||||
mapColour: Colour;
|
||||
layer: UInt;
|
||||
collisionMasks: List;
|
||||
|
|
@ -229,7 +233,9 @@ export namespace FactorioPrototype {
|
|||
}
|
||||
}
|
||||
|
||||
export type EventName = string;
|
||||
export type PrototypeId = string & { __PrototypeId__: void };
|
||||
|
||||
export type EventName = string & { __EventName__: void };
|
||||
|
||||
export type Byte = int8;
|
||||
|
||||
|
|
@ -238,22 +244,114 @@ export type MapChunkPosition = [
|
|||
y: Int,
|
||||
];
|
||||
|
||||
export type FactorioEntityData =
|
||||
| FactorioEntityData.Resource
|
||||
| FactorioEntityData.Standard;
|
||||
|
||||
export namespace FactorioEntityData {
|
||||
export enum Type {
|
||||
Resource = "kafkatorio.entity.FactorioEntityData.Resource",
|
||||
Standard = "kafkatorio.entity.FactorioEntityData.Standard",
|
||||
}
|
||||
|
||||
export interface Resource {
|
||||
type: FactorioEntityData.Type.Resource;
|
||||
protoId: PrototypeId;
|
||||
status?: EntityStatus | null;
|
||||
position: MapEntityPosition;
|
||||
amount: UInt;
|
||||
initialAmount?: UInt | null;
|
||||
}
|
||||
|
||||
export interface Standard {
|
||||
type: FactorioEntityData.Type.Standard;
|
||||
protoId: PrototypeId;
|
||||
status?: EntityStatus | null;
|
||||
position: MapEntityPosition;
|
||||
graphicsVariation?: UByte | null;
|
||||
health?: Float | null;
|
||||
isActive?: boolean | null;
|
||||
isRotatable?: boolean | null;
|
||||
lastUser?: UInt | null;
|
||||
localisedDescription?: string | null;
|
||||
localisedName?: string | null;
|
||||
}
|
||||
}
|
||||
|
||||
export interface MiningProperties {
|
||||
canBeMined: boolean;
|
||||
products: MinedProduct[] | null;
|
||||
}
|
||||
|
||||
export type List = any;
|
||||
export type List = any & { __List__: void };
|
||||
|
||||
export type PrototypeKey = Int;
|
||||
|
||||
export interface MinedProduct {
|
||||
type: MinedProductType;
|
||||
name: PrototypeName;
|
||||
amount: Double | null;
|
||||
export enum EntityStatus {
|
||||
CANT_DIVIDE_SEGMENTS = "CANT_DIVIDE_SEGMENTS",
|
||||
CHARGING = "CHARGING",
|
||||
CLOSED_BY_CIRCUIT_NETWORK = "CLOSED_BY_CIRCUIT_NETWORK",
|
||||
DISABLED = "DISABLED",
|
||||
DISABLED_BY_CONTROL_BEHAVIOR = "DISABLED_BY_CONTROL_BEHAVIOR",
|
||||
DISABLED_BY_SCRIPT = "DISABLED_BY_SCRIPT",
|
||||
DISCHARGING = "DISCHARGING",
|
||||
FLUID_INGREDIENT_SHORTAGE = "FLUID_INGREDIENT_SHORTAGE",
|
||||
FULLY_CHARGED = "FULLY_CHARGED",
|
||||
FULL_OUTPUT = "FULL_OUTPUT",
|
||||
ITEM_INGREDIENT_SHORTAGE = "ITEM_INGREDIENT_SHORTAGE",
|
||||
LAUNCHING_ROCKET = "LAUNCHING_ROCKET",
|
||||
LOW_INPUT_FLUID = "LOW_INPUT_FLUID",
|
||||
LOW_POWER = "LOW_POWER",
|
||||
LOW_TEMPERATURE = "LOW_TEMPERATURE",
|
||||
MARKED_FOR_DECONSTRUCTION = "MARKED_FOR_DECONSTRUCTION",
|
||||
MISSING_REQUIRED_FLUID = "MISSING_REQUIRED_FLUID",
|
||||
MISSING_SCIENCE_PACKS = "MISSING_SCIENCE_PACKS",
|
||||
NETWORKS_CONNECTED = "NETWORKS_CONNECTED",
|
||||
NETWORKS_DISCONNECTED = "NETWORKS_DISCONNECTED",
|
||||
NORMAL = "NORMAL",
|
||||
NOT_CONNECTED_TO_RAIL = "NOT_CONNECTED_TO_RAIL",
|
||||
NOT_PLUGGED_IN_ELECTRIC_NETWORK = "NOT_PLUGGED_IN_ELECTRIC_NETWORK",
|
||||
NO_AMMO = "NO_AMMO",
|
||||
NO_FUEL = "NO_FUEL",
|
||||
NO_INGREDIENTS = "NO_INGREDIENTS",
|
||||
NO_INPUT_FLUID = "NO_INPUT_FLUID",
|
||||
NO_MINABLE_RESOURCES = "NO_MINABLE_RESOURCES",
|
||||
NO_MODULES_TO_TRANSMIT = "NO_MODULES_TO_TRANSMIT",
|
||||
NO_POWER = "NO_POWER",
|
||||
NO_RECIPE = "NO_RECIPE",
|
||||
NO_RESEARCH_IN_PROGRESS = "NO_RESEARCH_IN_PROGRESS",
|
||||
OPENED_BY_CIRCUIT_NETWORK = "OPENED_BY_CIRCUIT_NETWORK",
|
||||
OUT_OF_LOGISTIC_NETWORK = "OUT_OF_LOGISTIC_NETWORK",
|
||||
PREPARING_ROCKET_FOR_LAUNCH = "PREPARING_ROCKET_FOR_LAUNCH",
|
||||
RECHARGING_AFTER_POWER_OUTAGE = "RECHARGING_AFTER_POWER_OUTAGE",
|
||||
TURNED_OFF_DURING_DAYTIME = "TURNED_OFF_DURING_DAYTIME",
|
||||
WAITING_FOR_SOURCE_ITEMS = "WAITING_FOR_SOURCE_ITEMS",
|
||||
WAITING_FOR_SPACE_IN_DESTINATION = "WAITING_FOR_SPACE_IN_DESTINATION",
|
||||
WAITING_FOR_TARGET_TO_BE_BUILT = "WAITING_FOR_TARGET_TO_BE_BUILT",
|
||||
WAITING_FOR_TRAIN = "WAITING_FOR_TRAIN",
|
||||
WAITING_TO_LAUNCH_ROCKET = "WAITING_TO_LAUNCH_ROCKET",
|
||||
WORKING = "WORKING",
|
||||
}
|
||||
|
||||
export enum MinedProductType {
|
||||
item = "item",
|
||||
fluid = "fluid",
|
||||
export type PrototypeKey = Int & { __PrototypeKey__: void };
|
||||
|
||||
export type MinedProduct =
|
||||
| MinedProduct.MinedProductFluid
|
||||
| MinedProduct.MinedProductItem;
|
||||
|
||||
export namespace MinedProduct {
|
||||
export enum Type {
|
||||
MinedProductFluid = "kafkatorio.resource.MinedProduct.MinedProductFluid",
|
||||
MinedProductItem = "kafkatorio.resource.MinedProduct.MinedProductItem",
|
||||
}
|
||||
|
||||
export interface MinedProductFluid {
|
||||
type: MinedProduct.Type.MinedProductFluid;
|
||||
amount?: Double | null;
|
||||
resultProtoId: PrototypeId;
|
||||
}
|
||||
|
||||
export interface MinedProductItem {
|
||||
type: MinedProduct.Type.MinedProductItem;
|
||||
amount?: Double | null;
|
||||
resultProtoId: PrototypeId;
|
||||
}
|
||||
}
|
||||
|
|
@ -101,7 +101,7 @@ export namespace EventUpdates {
|
|||
let countExpired = 0
|
||||
let countTotal = 0
|
||||
const expiredData: Array<KafkatorioKeyedPacketData> = []
|
||||
for (let [key, entry] of global.cache) {
|
||||
for (const [key, entry] of global.cache) {
|
||||
countTotal++
|
||||
if (this.isExpired(entry)) {
|
||||
countExpired++
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ script.on_event(
|
|||
const update: KafkatorioPacketData.ConsoleCommandUpdate = {
|
||||
type: Type.ConsoleCommandUpdate,
|
||||
|
||||
authorPlayerIndex: event.player_index ?? null,
|
||||
authorPlayerIndex: event.player_index ?? null,
|
||||
command: event.command,
|
||||
parameters: event.parameters,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,25 @@
|
|||
import {EventName} from "../types";
|
||||
import {Colour, MapChunkPosition} from "../../generated/kafkatorio-schema";
|
||||
import {DefinedEventName} from "../types";
|
||||
import {
|
||||
Colour,
|
||||
EntityStatus,
|
||||
EventName,
|
||||
FactorioEntityData,
|
||||
MapChunkPosition,
|
||||
PrototypeId,
|
||||
} from "../../generated/kafkatorio-schema";
|
||||
|
||||
export namespace Converters {
|
||||
|
||||
const mapEventIdToName = new LuaTable<defines.Events, EventName>()
|
||||
|
||||
import floor = math.floor;
|
||||
const mapEventIdToName = new LuaTable<defines.Events, DefinedEventName>()
|
||||
for (const [eventName, eventId] of pairs(defines.events)) {
|
||||
mapEventIdToName.set(eventId, eventName)
|
||||
}
|
||||
|
||||
|
||||
export function eventNameString(event: defines.Events): EventName {
|
||||
return mapEventIdToName.get(event)
|
||||
return mapEventIdToName.get(event) as EventName
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -60,10 +69,115 @@ export namespace Converters {
|
|||
|
||||
export function convertCollisionMaskToNames(cm: CollisionMask): string[] {
|
||||
let masks: string[] = []
|
||||
for (let [name] of pairs(cm)) {
|
||||
for (const [name] of pairs(cm)) {
|
||||
masks.push(name)
|
||||
}
|
||||
return masks
|
||||
}
|
||||
|
||||
|
||||
export function convertResourceEntity(
|
||||
entity: LuaEntity
|
||||
): FactorioEntityData.Resource | null {
|
||||
if (entity.type != "resource") {
|
||||
return null
|
||||
} else {
|
||||
return {
|
||||
type: FactorioEntityData.Type.Resource,
|
||||
protoId: prototypeId(entity.type, entity.name),
|
||||
status: convertEntityStatus(entity.status),
|
||||
amount: entity.amount,
|
||||
initialAmount: entity.initial_amount,
|
||||
position: convertMapTablePosition(entity.position),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function convertEntityStatus(
|
||||
status: defines.entity_status | undefined
|
||||
): EntityStatus | null {
|
||||
if (status == undefined) {
|
||||
return null
|
||||
} else {
|
||||
return EntityStatusRecord[status]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export function prototypeIdEntity(entity: LuaEntity): PrototypeId {
|
||||
return prototypeId(entity.type, entity.name)
|
||||
}
|
||||
|
||||
|
||||
export function prototypeId(type: string, name: string): PrototypeId {
|
||||
return `${type}/${name}` as PrototypeId
|
||||
}
|
||||
|
||||
|
||||
export function tilePositionToChunkPosition(mapPosition: TilePositionTable): MapChunkPosition {
|
||||
return [floor(mapPosition.x / 32), floor(mapPosition.y)]
|
||||
}
|
||||
|
||||
export function mapPositionToChunkPosition(mapPosition: MapPositionTable): MapChunkPosition {
|
||||
return [floor(mapPosition.x / 32), floor(mapPosition.y)]
|
||||
}
|
||||
|
||||
|
||||
export function entityStatus(status: defines.entity_status | undefined): EntityStatus | undefined {
|
||||
if (status == undefined) {
|
||||
return undefined
|
||||
} else {
|
||||
return EntityStatusRecord[status]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const EntityStatusRecord: Record<defines.entity_status, EntityStatus> = {
|
||||
//@formatter:off
|
||||
[ defines.entity_status.cant_divide_segments ]: EntityStatus.CANT_DIVIDE_SEGMENTS,
|
||||
[ defines.entity_status.charging ]: EntityStatus.CHARGING,
|
||||
[ defines.entity_status.closed_by_circuit_network ]: EntityStatus.CLOSED_BY_CIRCUIT_NETWORK,
|
||||
[ defines.entity_status.disabled ]: EntityStatus.DISABLED,
|
||||
[ defines.entity_status.disabled_by_control_behavior ]: EntityStatus.DISABLED_BY_CONTROL_BEHAVIOR,
|
||||
[ defines.entity_status.disabled_by_script ]: EntityStatus.DISABLED_BY_SCRIPT,
|
||||
[ defines.entity_status.discharging ]: EntityStatus.DISCHARGING,
|
||||
[ defines.entity_status.fluid_ingredient_shortage ]: EntityStatus.FLUID_INGREDIENT_SHORTAGE,
|
||||
[ defines.entity_status.fully_charged ]: EntityStatus.FULLY_CHARGED,
|
||||
[ defines.entity_status.full_output ]: EntityStatus.FULL_OUTPUT,
|
||||
[ defines.entity_status.item_ingredient_shortage ]: EntityStatus.ITEM_INGREDIENT_SHORTAGE,
|
||||
[ defines.entity_status.launching_rocket ]: EntityStatus.LAUNCHING_ROCKET,
|
||||
[ defines.entity_status.low_input_fluid ]: EntityStatus.LOW_INPUT_FLUID,
|
||||
[ defines.entity_status.low_power ]: EntityStatus.LOW_POWER,
|
||||
[ defines.entity_status.low_temperature ]: EntityStatus.LOW_TEMPERATURE,
|
||||
[ defines.entity_status.marked_for_deconstruction ]: EntityStatus.MARKED_FOR_DECONSTRUCTION,
|
||||
[ defines.entity_status.missing_required_fluid ]: EntityStatus.MISSING_REQUIRED_FLUID,
|
||||
[ defines.entity_status.missing_science_packs ]: EntityStatus.MISSING_SCIENCE_PACKS,
|
||||
[ defines.entity_status.networks_connected ]: EntityStatus.NETWORKS_CONNECTED,
|
||||
[ defines.entity_status.networks_disconnected ]: EntityStatus.NETWORKS_DISCONNECTED,
|
||||
[ defines.entity_status.normal ]: EntityStatus.NORMAL,
|
||||
[ defines.entity_status.not_connected_to_rail ]: EntityStatus.NOT_CONNECTED_TO_RAIL,
|
||||
[ defines.entity_status.not_plugged_in_electric_network ]: EntityStatus.NOT_PLUGGED_IN_ELECTRIC_NETWORK,
|
||||
[ defines.entity_status.no_ammo ]: EntityStatus.NO_AMMO,
|
||||
[ defines.entity_status.no_fuel ]: EntityStatus.NO_FUEL,
|
||||
[ defines.entity_status.no_ingredients ]: EntityStatus.NO_INGREDIENTS,
|
||||
[ defines.entity_status.no_input_fluid ]: EntityStatus.NO_INPUT_FLUID,
|
||||
[ defines.entity_status.no_minable_resources ]: EntityStatus.NO_MINABLE_RESOURCES,
|
||||
[ defines.entity_status.no_modules_to_transmit ]: EntityStatus.NO_MODULES_TO_TRANSMIT,
|
||||
[ defines.entity_status.no_power ]: EntityStatus.NO_POWER,
|
||||
[ defines.entity_status.no_recipe ]: EntityStatus.NO_RECIPE,
|
||||
[ defines.entity_status.no_research_in_progress ]: EntityStatus.NO_RESEARCH_IN_PROGRESS,
|
||||
[ defines.entity_status.opened_by_circuit_network ]: EntityStatus.OPENED_BY_CIRCUIT_NETWORK,
|
||||
[ defines.entity_status.out_of_logistic_network ]: EntityStatus.OUT_OF_LOGISTIC_NETWORK,
|
||||
[ defines.entity_status.preparing_rocket_for_launch ]: EntityStatus.PREPARING_ROCKET_FOR_LAUNCH,
|
||||
[ defines.entity_status.recharging_after_power_outage ]: EntityStatus.RECHARGING_AFTER_POWER_OUTAGE,
|
||||
[ defines.entity_status.turned_off_during_daytime ]: EntityStatus.TURNED_OFF_DURING_DAYTIME,
|
||||
[ defines.entity_status.waiting_for_source_items ]: EntityStatus.WAITING_FOR_SOURCE_ITEMS,
|
||||
[ defines.entity_status.waiting_for_space_in_destination ]: EntityStatus.WAITING_FOR_SPACE_IN_DESTINATION,
|
||||
[ defines.entity_status.waiting_for_target_to_be_built ]: EntityStatus.WAITING_FOR_TARGET_TO_BE_BUILT,
|
||||
[ defines.entity_status.waiting_for_train ]: EntityStatus.WAITING_FOR_TRAIN,
|
||||
[ defines.entity_status.waiting_to_launch_rocket ]: EntityStatus.WAITING_TO_LAUNCH_ROCKET,
|
||||
[ defines.entity_status.working ]: EntityStatus.WORKING,
|
||||
//@formatter:on
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,97 +1,276 @@
|
|||
import {Converters} from "./converters";
|
||||
import EventDataCache from "../cache/EventDataCache";
|
||||
import {
|
||||
EntityUpdateKey,
|
||||
KafkatorioPacketData
|
||||
EventName,
|
||||
FactorioEntityData,
|
||||
KafkatorioPacketData,
|
||||
MapChunkPosition,
|
||||
PrototypeId,
|
||||
Tick,
|
||||
} from "../../generated/kafkatorio-schema";
|
||||
|
||||
|
||||
export class EntityUpdatesHandler {
|
||||
|
||||
|
||||
handleMinedTileEvent(event: OnPlayerMinedTileEvent | OnRobotMinedTileEvent) {
|
||||
|
||||
const surface = game.get_surface(event.surface_index)
|
||||
if (surface == null) {
|
||||
return
|
||||
}
|
||||
|
||||
const tilesByChunk: LuaTable<MapChunkPosition, OldTileAndPosition> = new LuaTable<MapChunkPosition, OldTileAndPosition>()
|
||||
|
||||
for (const tile of event.tiles) {
|
||||
const chunkPosition = Converters.tilePositionToChunkPosition(tile.position)
|
||||
tilesByChunk.set(chunkPosition, tile)
|
||||
}
|
||||
|
||||
for (const [chunkPos, tiles] of pairs(tilesByChunk)) {
|
||||
|
||||
const entities: FactorioEntityData.Resource[] = []
|
||||
|
||||
for (const tile in tiles) {
|
||||
const resources: FactorioEntityData.Resource[] = EntityUpdatesHandler.getTileResourceEntities(surface, chunkPos)
|
||||
for (const resource of resources) {
|
||||
entities[entities.length] = resource
|
||||
}
|
||||
}
|
||||
|
||||
EntityUpdatesHandler.throttleResourceEntitiesUpdate(
|
||||
Converters.eventNameString(event.name),
|
||||
event.tick as Tick,
|
||||
event.surface_index,
|
||||
[chunkPos[0], chunkPos[1]],
|
||||
entities,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
handleMinedEntityEvent(
|
||||
event: OnPlayerMinedEntityEvent | OnRobotMinedEntityEvent,
|
||||
) {
|
||||
EntityUpdatesHandler.throttleEntityUpdate(event, event.entity)
|
||||
}
|
||||
|
||||
|
||||
handleBuiltEntityEvent(
|
||||
event: OnBuiltEntityEvent | OnRobotBuiltEntityEvent,
|
||||
) {
|
||||
EntityUpdatesHandler.throttleEntityUpdate(event, event.created_entity)
|
||||
}
|
||||
|
||||
|
||||
handleChunkGeneratedEvent(event: OnChunkGeneratedEvent) {
|
||||
const entities = EntityUpdatesHandler.getAreaResourceEntities(event.surface, event.area)
|
||||
EntityUpdatesHandler.throttleResourceEntitiesUpdate(
|
||||
Converters.eventNameString(event.name),
|
||||
event.tick as Tick,
|
||||
event.surface.index,
|
||||
[event.position.x, event.position.y],
|
||||
entities,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private static throttleEntityUpdate(event: EntityUpdateEvent, entity: LuaEntity) {
|
||||
|
||||
if (entity.unit_number == undefined) {
|
||||
// ignore for now - I'm not sure how to track entities without a unit_number
|
||||
return
|
||||
}
|
||||
const eventName = Converters.eventNameString(event.name)
|
||||
|
||||
const entityUpdateKey: KafkatorioPacketData.MapChunkEntityUpdate["key"] = {
|
||||
protoId: Converters.prototypeIdEntity(entity),
|
||||
surfaceIndex: entity.surface.index,
|
||||
chunkPosition: Converters.mapPositionToChunkPosition(entity.position)
|
||||
}
|
||||
|
||||
const entityUpdate: FactorioEntityData.Standard = {
|
||||
type: FactorioEntityData.Type.Standard,
|
||||
protoId: Converters.prototypeIdEntity(entity),
|
||||
graphicsVariation: entity.graphics_variation,
|
||||
health: entity.health,
|
||||
isActive: entity.active,
|
||||
isRotatable: entity.rotatable,
|
||||
lastUser: entity.last_user?.index,
|
||||
localisedDescription: null,
|
||||
localisedName: null,
|
||||
position: [entity.position.x, entity.position.y],
|
||||
status: Converters.entityStatus(entity.status),
|
||||
}
|
||||
|
||||
const entityKey: string = `${entity.unit_number}`
|
||||
|
||||
EventDataCache.throttle<KafkatorioPacketData.MapChunkEntityUpdate>(
|
||||
entityUpdateKey,
|
||||
KafkatorioPacketData.Type.MapChunkEntityUpdate,
|
||||
data => {
|
||||
|
||||
data.events ??= {}
|
||||
data.events[eventName] ??= []
|
||||
data.events[eventName].push(event.tick)
|
||||
|
||||
data.distinctEntities ??= {}
|
||||
data.distinctEntities[entityKey] = entityUpdate
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private static throttleResourceEntitiesUpdate(
|
||||
eventName: EventName,
|
||||
eventTick: Tick,
|
||||
surfaceIndex: SurfaceIndex,
|
||||
chunkPosition: MapChunkPosition,
|
||||
entities: FactorioEntityData.Resource[],
|
||||
) {
|
||||
|
||||
// first group by prototype ID
|
||||
const entitiesByProtoId: { [protoId: PrototypeId]: FactorioEntityData.Resource[] } = {}
|
||||
|
||||
for (const entity of entities) {
|
||||
entitiesByProtoId[entity.protoId] ??= []
|
||||
entitiesByProtoId[entity.protoId].push(entity)
|
||||
}
|
||||
|
||||
for (const [protoId, resourceEntities] of pairs(entitiesByProtoId)) {
|
||||
|
||||
// create an update for each prototype
|
||||
|
||||
const entityUpdateKey: KafkatorioPacketData.MapChunkEntityUpdate["key"] = {
|
||||
protoId: protoId,
|
||||
surfaceIndex: surfaceIndex,
|
||||
chunkPosition: [chunkPosition[0], chunkPosition[1]],
|
||||
}
|
||||
|
||||
EventDataCache.throttle<KafkatorioPacketData.MapChunkEntityUpdate>(
|
||||
entityUpdateKey,
|
||||
KafkatorioPacketData.Type.MapChunkEntityUpdate,
|
||||
data => {
|
||||
|
||||
data.events ??= {}
|
||||
data.events[eventName] ??= []
|
||||
data.events[eventName].push(eventTick)
|
||||
|
||||
for (const entity of resourceEntities) {
|
||||
const [x, y] = entity.position
|
||||
const entityKey: string = `${x},${y}`
|
||||
|
||||
data.distinctEntities ??= {}
|
||||
data.distinctEntities[entityKey] = entity
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/** Get all resources on a tile-position */
|
||||
private static getTileResourceEntities(
|
||||
surface: LuaSurface,
|
||||
mapPosition: MapPosition,
|
||||
): FactorioEntityData.Resource[] {
|
||||
|
||||
const rawEntities = surface.find_entities_filtered({
|
||||
position: mapPosition,
|
||||
collision_mask: "resource-layer",
|
||||
}
|
||||
)
|
||||
|
||||
const resources: FactorioEntityData.Resource[] = []
|
||||
|
||||
for (const rawEntity of rawEntities) {
|
||||
const entity = Converters.convertResourceEntity(rawEntity)
|
||||
if (entity != null) {
|
||||
resources[resources.length] = entity
|
||||
}
|
||||
}
|
||||
|
||||
return resources
|
||||
}
|
||||
|
||||
|
||||
/** Get all resources within a map-area */
|
||||
private static getAreaResourceEntities(
|
||||
surface: LuaSurface,
|
||||
area: BoundingBoxTable,
|
||||
): FactorioEntityData.Resource[] {
|
||||
|
||||
const rawEntities = surface.find_entities_filtered({
|
||||
area: area,
|
||||
collision_mask: "resource-layer",
|
||||
}
|
||||
)
|
||||
|
||||
const resources: FactorioEntityData.Resource[] = []
|
||||
|
||||
for (const rawEntity of rawEntities) {
|
||||
const entity = Converters.convertResourceEntity(rawEntity)
|
||||
if (entity != null) {
|
||||
resources[resources.length] = entity
|
||||
}
|
||||
}
|
||||
|
||||
return resources
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const EntityUpdates = new EntityUpdatesHandler()
|
||||
|
||||
export default EntityUpdates
|
||||
|
||||
|
||||
script.on_event(defines.events.on_built_entity, (e: OnBuiltEntityEvent) => {
|
||||
log(`on_built_entity ${e.tick}`)
|
||||
handleBuiltEntityEvent(e)
|
||||
EntityUpdates.handleBuiltEntityEvent(e)
|
||||
})
|
||||
|
||||
|
||||
script.on_event(defines.events.on_robot_built_entity, (e: OnRobotBuiltEntityEvent) => {
|
||||
log(`on_robot_built_entity ${e.tick}`)
|
||||
handleBuiltEntityEvent(e)
|
||||
EntityUpdates.handleBuiltEntityEvent(e)
|
||||
})
|
||||
|
||||
|
||||
function handleBuiltEntityEvent(event: OnBuiltEntityEvent | OnRobotBuiltEntityEvent) {
|
||||
const eventName = Converters.eventNameString(event.name)
|
||||
|
||||
if (event.created_entity.unit_number == undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const key: EntityUpdateKey = {
|
||||
name: event.created_entity.name,
|
||||
protoType: event.created_entity.prototype.name,
|
||||
unitNumber: event.created_entity.unit_number
|
||||
}
|
||||
|
||||
EventDataCache.debounce<KafkatorioPacketData.EntityUpdate>(
|
||||
key,
|
||||
KafkatorioPacketData.Type.EntityUpdate,
|
||||
data => {
|
||||
|
||||
data.events ??= {}
|
||||
data.events[eventName] ??= []
|
||||
data.events[eventName].push(event.tick)
|
||||
|
||||
data.chunkPosition = Converters.convertMapTablePosition(event.created_entity.position)
|
||||
data.graphicsVariation = event.created_entity.graphics_variation
|
||||
data.health = event.created_entity.health
|
||||
data.isActive = event.created_entity.active
|
||||
data.isRotatable = event.created_entity.rotatable
|
||||
data.lastUser = event.created_entity.last_user?.index
|
||||
data.prototype = event.created_entity.prototype.name
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
script.on_event(defines.events.on_player_mined_entity, (e: OnPlayerMinedEntityEvent) => {
|
||||
log(`on_player_mined_entity ${e.tick}`)
|
||||
handleMinedEntityEvent(e)
|
||||
EntityUpdates.handleMinedEntityEvent(e)
|
||||
})
|
||||
|
||||
|
||||
script.on_event(defines.events.on_robot_mined_entity, (e: OnRobotMinedEntityEvent) => {
|
||||
log(`on_robot_mined_entity ${e.tick}`)
|
||||
handleMinedEntityEvent(e)
|
||||
EntityUpdates.handleMinedEntityEvent(e)
|
||||
})
|
||||
|
||||
|
||||
function handleMinedEntityEvent(event: OnPlayerMinedEntityEvent | OnRobotMinedEntityEvent) {
|
||||
const eventName = Converters.eventNameString(event.name)
|
||||
type EntityUpdateEvent =
|
||||
| OnBuiltEntityEvent
|
||||
|
||||
if (event.entity.unit_number == undefined) {
|
||||
return
|
||||
}
|
||||
| OnEntityClonedEvent
|
||||
| OnEntityDamagedEvent
|
||||
| OnEntityDestroyedEvent
|
||||
| OnEntityDiedEvent
|
||||
| OnEntityLogisticSlotChangedEvent
|
||||
| OnEntityRenamedEvent
|
||||
| OnEntitySettingsPastedEvent
|
||||
| OnEntitySpawnedEvent
|
||||
|
||||
const key: EntityUpdateKey = {
|
||||
name: event.entity.name,
|
||||
protoType: event.entity.prototype.name,
|
||||
unitNumber: event.entity.unit_number
|
||||
}
|
||||
| OnPlayerMinedEntityEvent
|
||||
| OnPlayerRepairedEntityEvent
|
||||
| OnPlayerRotatedEntityEvent
|
||||
|
||||
EventDataCache.debounce<KafkatorioPacketData.EntityUpdate>(
|
||||
key,
|
||||
KafkatorioPacketData.Type.EntityUpdate,
|
||||
data => {
|
||||
| OnRobotMinedEntityEvent
|
||||
| OnRobotBuiltEntityEvent
|
||||
|
||||
data.events ??= {}
|
||||
data.events[eventName] ??= []
|
||||
data.events[eventName].push(event.tick)
|
||||
| OnPostEntityDiedEvent
|
||||
| OnPreEntitySettingsPastedEvent
|
||||
|
||||
data.chunkPosition = Converters.convertMapTablePosition(event.entity.position)
|
||||
data.graphicsVariation = event.entity.graphics_variation
|
||||
data.health = event.entity.health
|
||||
data.isActive = event.entity.active
|
||||
data.isRotatable = event.entity.rotatable
|
||||
data.lastUser = event.entity.last_user?.index
|
||||
data.prototype = event.entity.prototype.name
|
||||
}
|
||||
)
|
||||
}
|
||||
| OnSelectedEntityChangedEvent
|
||||
| OnTriggerCreatedEntityEvent
|
||||
|
|
|
|||
|
|
@ -3,129 +3,270 @@ import {Converters} from "./converters";
|
|||
import EventUpdatesManager, {EventUpdates} from "../cache/EventDataCache";
|
||||
import {
|
||||
KafkatorioPacketData,
|
||||
MapChunkPosition
|
||||
MapChunkPosition, PrototypeId,
|
||||
PrototypeKey,
|
||||
} from "../../generated/kafkatorio-schema";
|
||||
import EntityUpdates from "./entityUpdates";
|
||||
import CacheKey = EventUpdates.PacketKey;
|
||||
import Type = KafkatorioPacketData.Type;
|
||||
|
||||
|
||||
type MapChunkUpdater = (data: KafkatorioPacketData.MapChunkUpdate) => void
|
||||
type MapChunkTileUpdater = (data: KafkatorioPacketData.MapChunkTileUpdate) => void
|
||||
|
||||
|
||||
type MapTileUpdateEvent =
|
||||
| OnChunkGeneratedEvent
|
||||
export type MapTileChangeEvent =
|
||||
| OnPlayerBuiltTileEvent
|
||||
| OnPlayerMinedEntityEvent
|
||||
| OnPlayerMinedTileEvent
|
||||
| OnRobotBuiltTileEvent
|
||||
| OnRobotMinedEntityEvent
|
||||
| OnRobotMinedTileEvent
|
||||
| ScriptRaisedSetTilesEvent
|
||||
|
||||
|
||||
function mapTilesUpdateDebounce(
|
||||
surface: LuaSurface | undefined,
|
||||
chunkPosition: MapChunkPosition,
|
||||
tiles: TileRead[],
|
||||
event: MapTileUpdateEvent,
|
||||
updater?: MapChunkUpdater,
|
||||
expirationDurationTicks?: uint,
|
||||
) {
|
||||
export type MapChunkUpdateEvent =
|
||||
| OnChunkChartedEvent
|
||||
| OnChunkDeletedEvent
|
||||
| OnChunkGeneratedEvent
|
||||
| OnPreChunkDeletedEvent
|
||||
|
||||
if (surface == undefined) {
|
||||
return
|
||||
|
||||
class MapChunkUpdateHandler {
|
||||
|
||||
handleChunkGeneratedEvent(
|
||||
event: OnChunkGeneratedEvent,
|
||||
expirationDurationTicks?: uint,
|
||||
) {
|
||||
const tiles = event.surface.find_tiles_filtered({area: event.area})
|
||||
|
||||
MapChunkUpdateHandler.mapTilesUpdateDebounce(
|
||||
event.surface,
|
||||
Converters.convertMapTablePosition(event.position),
|
||||
tiles,
|
||||
event,
|
||||
undefined,
|
||||
expirationDurationTicks,
|
||||
)
|
||||
}
|
||||
|
||||
const eventName = Converters.eventNameString(event.name)
|
||||
|
||||
const key: KafkatorioPacketData.MapChunkUpdate["key"] = {
|
||||
surfaceIndex: surface.index,
|
||||
chunkPosition: chunkPosition,
|
||||
handleBuiltTileEvent(event: OnPlayerBuiltTileEvent | OnRobotBuiltTileEvent) {
|
||||
|
||||
const surface = game.get_surface(event.surface_index)
|
||||
if (surface == undefined) {
|
||||
const eventName = Converters.eventNameString(event.name)
|
||||
log(`[handleBuiltTileEvent] undefined surface ${eventName}`)
|
||||
return
|
||||
}
|
||||
|
||||
const tiles: TileRead[] = Converters.convertPlacedTiles(event.tile, event.tiles)
|
||||
const groupedTiles = MapChunkUpdateHandler.groupTiles(tiles)
|
||||
|
||||
for (const [chunkPos, tiles] of groupedTiles) {
|
||||
MapChunkUpdateHandler.mapTilesUpdateDebounce(
|
||||
surface,
|
||||
chunkPos,
|
||||
tiles,
|
||||
event,
|
||||
(data => {
|
||||
if (isEventType(event, defines.events.on_player_built_tile)) {
|
||||
data.player = event.player_index
|
||||
return
|
||||
}
|
||||
if (isEventType(event, defines.events.on_robot_built_tile)) {
|
||||
data.robot = {
|
||||
unitNumber: event.robot?.unit_number ?? null,
|
||||
protoId: Converters.prototypeId(event.robot.type, event.robot.name),
|
||||
}
|
||||
return
|
||||
}
|
||||
// this line will not compile if the previous 'if' statements are not exhaustive
|
||||
// noinspection UnnecessaryLocalVariableJS,JSUnusedLocalSymbols
|
||||
const exhaustiveCheck: never = event
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
EventUpdatesManager.debounce<KafkatorioPacketData.MapChunkUpdate>(
|
||||
key,
|
||||
Type.MapChunkUpdate,
|
||||
data => {
|
||||
|
||||
// tile dictionary update
|
||||
if (data.tileDictionary == undefined) {
|
||||
data.tileDictionary = {
|
||||
tilesXY: {},
|
||||
protos: {},
|
||||
}
|
||||
}
|
||||
handleMinedTileEvent(event: OnPlayerMinedTileEvent | OnRobotMinedTileEvent) {
|
||||
const surface = game.get_surface(event.surface_index)
|
||||
if (surface == undefined) {
|
||||
const eventName = Converters.eventNameString(event.name)
|
||||
log(`[handleMinedTileEvent] undefined surface ${eventName}`)
|
||||
return
|
||||
}
|
||||
|
||||
let protosCount: uint = table_size(data.tileDictionary.protos)
|
||||
for (const tile of tiles) {
|
||||
data.tileDictionary.protos[tile.name] ??= protosCount++
|
||||
const tiles: TileRead[] = Converters.convertRemovedTiles(surface, event.tiles)
|
||||
const groupedTiles = MapChunkUpdateHandler.groupTiles(tiles)
|
||||
|
||||
const protoKey = data.tileDictionary.protos[tile.name]
|
||||
for (const [chunkPos, tiles] of groupedTiles) {
|
||||
|
||||
const xString = `${tile.position.x}`
|
||||
const yString = `${tile.position.y}`
|
||||
|
||||
data.tileDictionary.tilesXY[xString] ??= {}
|
||||
data.tileDictionary.tilesXY[xString][yString] = protoKey
|
||||
}
|
||||
|
||||
// events count update
|
||||
data.events ??= {}
|
||||
data.events[eventName] ??= []
|
||||
data.events[eventName].push(event.tick)
|
||||
|
||||
// apply mutator
|
||||
if (updater != undefined) {
|
||||
updater(data)
|
||||
}
|
||||
},
|
||||
expirationDurationTicks,
|
||||
)
|
||||
}
|
||||
MapChunkUpdateHandler.mapTilesUpdateDebounce(
|
||||
surface,
|
||||
chunkPos,
|
||||
tiles,
|
||||
event,
|
||||
(data => {
|
||||
if (isEventType(event, defines.events.on_player_mined_tile)) {
|
||||
data.player = event.player_index
|
||||
return
|
||||
}
|
||||
if (isEventType(event, defines.events.on_robot_mined_tile)) {
|
||||
data.robot = {
|
||||
unitNumber: event.robot?.unit_number ?? null,
|
||||
protoId: Converters.prototypeId(event.robot.type, event.robot.name),
|
||||
}
|
||||
return
|
||||
}
|
||||
// this line will not compile if the previous 'if' statements are not exhaustive
|
||||
// noinspection UnnecessaryLocalVariableJS,JSUnusedLocalSymbols
|
||||
const exhaustiveCheck: never = event
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function getTiles(
|
||||
surface: LuaSurface | undefined,
|
||||
area: BoundingBoxTable
|
||||
): LuaTile[] {
|
||||
return surface?.find_tiles_filtered({
|
||||
area: area,
|
||||
// collision_mask: ["ground-tile", "water-tile"]
|
||||
handleScriptSetTilesEvent(event: ScriptRaisedSetTilesEvent) {
|
||||
|
||||
const surface = game.get_surface(event.surface_index)
|
||||
if (surface == undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const groupedTiles = MapChunkUpdateHandler.groupTiles(event.tiles)
|
||||
|
||||
for (const [chunkPos, tiles] of groupedTiles) {
|
||||
MapChunkUpdateHandler.mapTilesUpdateDebounce(surface, chunkPos, tiles, event)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
handlePreChunkDeleted(event: OnPreChunkDeletedEvent) {
|
||||
|
||||
const surface = game.get_surface(event.surface_index)
|
||||
if (surface == undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const position of event.positions) {
|
||||
const key: CacheKey<KafkatorioPacketData.MapChunkTileUpdate> = {
|
||||
surfaceIndex: surface.index,
|
||||
chunkPosition: [position.x, position.y],
|
||||
}
|
||||
) ?? []
|
||||
|
||||
EventUpdatesManager.debounce<KafkatorioPacketData.MapChunkTileUpdate>(
|
||||
key,
|
||||
Type.MapChunkTileUpdate,
|
||||
data => data.isDeleted = true,
|
||||
0, // deletion is important - emit ASAP
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static mapTilesUpdateDebounce(
|
||||
surface: LuaSurface | undefined,
|
||||
chunkPosition: MapChunkPosition,
|
||||
tiles: TileRead[],
|
||||
event: MapChunkUpdateEvent | MapTileChangeEvent,
|
||||
updater?: MapChunkTileUpdater,
|
||||
expirationDurationTicks?: uint,
|
||||
) {
|
||||
|
||||
if (surface == undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const eventName = Converters.eventNameString(event.name)
|
||||
|
||||
const key: KafkatorioPacketData.MapChunkTileUpdate["key"] = {
|
||||
surfaceIndex: surface.index,
|
||||
chunkPosition: chunkPosition,
|
||||
}
|
||||
|
||||
EventUpdatesManager.debounce<KafkatorioPacketData.MapChunkTileUpdate>(
|
||||
key,
|
||||
Type.MapChunkTileUpdate,
|
||||
data => {
|
||||
|
||||
// tile dictionary update
|
||||
if (data.tileDictionary == undefined) {
|
||||
data.tileDictionary = {
|
||||
tilesXY: {},
|
||||
protos: {},
|
||||
}
|
||||
}
|
||||
|
||||
let protosCount: uint = table_size(data.tileDictionary.protos)
|
||||
for (const tile of tiles) {
|
||||
const protoId = Converters.prototypeId("tile", tile.name)
|
||||
|
||||
if (data.tileDictionary.protos[protoId] == null) {
|
||||
|
||||
}
|
||||
data.tileDictionary.protos[protoId] ??= protosCount++ as PrototypeKey
|
||||
|
||||
const protoKey = data.tileDictionary.protos[protoId]
|
||||
|
||||
const xString = `${tile.position.x}`
|
||||
const yString = `${tile.position.y}`
|
||||
|
||||
data.tileDictionary.tilesXY[xString] ??= {}
|
||||
data.tileDictionary.tilesXY[xString][yString] = protoKey
|
||||
}
|
||||
|
||||
// events count update
|
||||
data.events ??= {}
|
||||
data.events[eventName] ??= []
|
||||
data.events[eventName].push(event.tick)
|
||||
|
||||
// apply mutator
|
||||
if (updater != undefined) {
|
||||
updater(data)
|
||||
}
|
||||
},
|
||||
expirationDurationTicks,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
private static groupTiles(tiles: TileRead[]): Map<MapChunkPosition, TileRead[]> {
|
||||
|
||||
const mapChunkPositionToTiles = new Map<MapChunkPosition, TileRead[]>()
|
||||
for (const tile of tiles) {
|
||||
const chunkPosition: MapChunkPosition = [
|
||||
math.floor((tile.position.x / 32)),
|
||||
math.floor((tile.position.y / 32)),
|
||||
]
|
||||
if (!mapChunkPositionToTiles.has(chunkPosition)) {
|
||||
mapChunkPositionToTiles.set(chunkPosition, [])
|
||||
}
|
||||
mapChunkPositionToTiles.get(chunkPosition)?.push(tile)
|
||||
}
|
||||
return mapChunkPositionToTiles
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
function getSurface(surfaceIndex: uint): LuaSurface | undefined {
|
||||
return game.surfaces[surfaceIndex]
|
||||
}
|
||||
const MapChunkUpdate = new MapChunkUpdateHandler()
|
||||
|
||||
export default MapChunkUpdate
|
||||
|
||||
|
||||
script.on_event(
|
||||
defines.events.on_chunk_generated,
|
||||
(e: OnChunkGeneratedEvent) => {
|
||||
log(`on_chunk_generated ${e.tick}`)
|
||||
handleChunkGeneratedEvent(e)
|
||||
MapChunkUpdate.handleChunkGeneratedEvent(e)
|
||||
EntityUpdates.handleChunkGeneratedEvent(e)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
export function handleChunkGeneratedEvent(
|
||||
event: OnChunkGeneratedEvent,
|
||||
expirationDurationTicks?: uint,
|
||||
) {
|
||||
let tiles = getTiles(event.surface, event.area)
|
||||
mapTilesUpdateDebounce(
|
||||
event.surface,
|
||||
Converters.convertMapTablePosition(event.position),
|
||||
tiles,
|
||||
event,
|
||||
undefined,
|
||||
expirationDurationTicks,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// script.on_event(
|
||||
// defines.events.on_chunk_charted,
|
||||
// (e: OnChunkChartedEvent) => {
|
||||
|
|
@ -144,169 +285,38 @@ export function handleChunkGeneratedEvent(
|
|||
// )
|
||||
|
||||
|
||||
script.on_event(
|
||||
defines.events.script_raised_set_tiles,
|
||||
(event: ScriptRaisedSetTilesEvent) => {
|
||||
log(`script_raised_set_tiles ${event.tick}`)
|
||||
|
||||
const surface = getSurface(event.surface_index)
|
||||
if (surface == undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
const groupedTiles = groupTiles(event.tiles)
|
||||
|
||||
for (const [chunkPos, tiles] of groupedTiles) {
|
||||
mapTilesUpdateDebounce(surface, chunkPos, tiles, event)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
function groupTiles(tiles: TileRead[]): Map<MapChunkPosition, TileRead[]> {
|
||||
|
||||
const mapChunkPositionToTiles = new Map<MapChunkPosition, TileRead[]>()
|
||||
for (const tile of tiles) {
|
||||
const chunkPosition: MapChunkPosition = [
|
||||
math.floor((tile.position.x / 32)),
|
||||
math.floor((tile.position.y / 32)),
|
||||
]
|
||||
if (!mapChunkPositionToTiles.has(chunkPosition)) {
|
||||
mapChunkPositionToTiles.set(chunkPosition, [])
|
||||
}
|
||||
// https://github.com/TypeScriptToLua/TypeScriptToLua/issues/1256
|
||||
mapChunkPositionToTiles.get(chunkPosition)?.push(tile)
|
||||
// const tiles = mapChunkPositionToTiles.get(chunkPosition)
|
||||
// if (tiles != null) {
|
||||
// tiles.push(tile)
|
||||
// }
|
||||
}
|
||||
return mapChunkPositionToTiles
|
||||
}
|
||||
script.on_event(defines.events.script_raised_set_tiles, (event: ScriptRaisedSetTilesEvent) => {
|
||||
log(`script_raised_set_tiles ${event.tick}`)
|
||||
MapChunkUpdate.handleScriptSetTilesEvent(event)
|
||||
})
|
||||
|
||||
|
||||
script.on_event(defines.events.on_player_built_tile, (e: OnPlayerBuiltTileEvent) => {
|
||||
log(`on_player_built_tile ${e.tick}`)
|
||||
handleBuiltTileEvent(e)
|
||||
MapChunkUpdate.handleBuiltTileEvent(e)
|
||||
})
|
||||
|
||||
|
||||
script.on_event(defines.events.on_robot_built_tile, (e: OnRobotBuiltTileEvent) => {
|
||||
log(`on_robot_built_tile ${e.tick}`)
|
||||
handleBuiltTileEvent(e)
|
||||
MapChunkUpdate.handleBuiltTileEvent(e)
|
||||
})
|
||||
|
||||
function handleBuiltTileEvent(event: OnPlayerBuiltTileEvent | OnRobotBuiltTileEvent) {
|
||||
|
||||
const surface = getSurface(event.surface_index)
|
||||
if (surface == undefined) {
|
||||
const eventName = Converters.eventNameString(event.name)
|
||||
log(`[handleBuiltTileEvent] undefined surface ${eventName}`)
|
||||
return
|
||||
}
|
||||
|
||||
const tiles: TileRead[] = Converters.convertPlacedTiles(event.tile, event.tiles)
|
||||
const groupedTiles = groupTiles(tiles)
|
||||
|
||||
for (const [chunkPos, tiles] of groupedTiles) {
|
||||
mapTilesUpdateDebounce(
|
||||
surface,
|
||||
chunkPos,
|
||||
tiles,
|
||||
event,
|
||||
(data => {
|
||||
if (isEventType(event, defines.events.on_player_built_tile)) {
|
||||
data.player = event.player_index
|
||||
return
|
||||
}
|
||||
if (isEventType(event, defines.events.on_robot_built_tile)) {
|
||||
data.robot = {
|
||||
unitNumber: event.robot?.unit_number ?? null,
|
||||
name: event.robot.name,
|
||||
protoType: event.robot.type,
|
||||
}
|
||||
return
|
||||
}
|
||||
// this line will not compile if the previous 'if' statements are not exhaustive
|
||||
// noinspection UnnecessaryLocalVariableJS,JSUnusedLocalSymbols
|
||||
const exhaustiveCheck: never = event
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
script.on_event(defines.events.on_player_mined_tile, (e: OnPlayerMinedTileEvent) => {
|
||||
log(`on_player_mined_tile ${e.tick}`)
|
||||
handleMinedTileEvent(e)
|
||||
MapChunkUpdate.handleMinedTileEvent(e)
|
||||
EntityUpdates.handleMinedTileEvent(e)
|
||||
})
|
||||
|
||||
|
||||
script.on_event(defines.events.on_robot_mined_tile, (e: OnRobotMinedTileEvent) => {
|
||||
log(`on_robot_mined_tile ${e.tick}`)
|
||||
handleMinedTileEvent(e)
|
||||
MapChunkUpdate.handleMinedTileEvent(e)
|
||||
})
|
||||
|
||||
function handleMinedTileEvent(event: OnPlayerMinedTileEvent | OnRobotMinedTileEvent) {
|
||||
const surface = getSurface(event.surface_index)
|
||||
if (surface == undefined) {
|
||||
const eventName = Converters.eventNameString(event.name)
|
||||
log(`[handleMinedTileEvent] undefined surface ${eventName}`)
|
||||
return
|
||||
}
|
||||
|
||||
const tiles: TileRead[] = Converters.convertRemovedTiles(surface, event.tiles)
|
||||
const groupedTiles = groupTiles(tiles)
|
||||
|
||||
for (const [chunkPos, tiles] of groupedTiles) {
|
||||
mapTilesUpdateDebounce(
|
||||
surface,
|
||||
chunkPos,
|
||||
tiles,
|
||||
event,
|
||||
(data => {
|
||||
if (isEventType(event, defines.events.on_player_mined_tile)) {
|
||||
data.player = event.player_index
|
||||
return
|
||||
}
|
||||
if (isEventType(event, defines.events.on_robot_mined_tile)) {
|
||||
data.robot = {
|
||||
unitNumber: event.robot?.unit_number ?? null,
|
||||
name: event.robot.name,
|
||||
protoType: event.robot.type,
|
||||
}
|
||||
return
|
||||
}
|
||||
// this line will not compile if the previous 'if' statements are not exhaustive
|
||||
// noinspection UnnecessaryLocalVariableJS,JSUnusedLocalSymbols
|
||||
const exhaustiveCheck: never = event
|
||||
}
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
script.on_event(
|
||||
defines.events.on_pre_chunk_deleted,
|
||||
(e: OnPreChunkDeletedEvent) => {
|
||||
log(`on_pre_chunk_deleted ${e.tick}`)
|
||||
|
||||
const surface = getSurface(e.surface_index)
|
||||
if (surface == undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
for (const position of e.positions) {
|
||||
const key: CacheKey<KafkatorioPacketData.MapChunkUpdate> = {
|
||||
surfaceIndex: surface.index,
|
||||
chunkPosition: [position.x, position.y],
|
||||
}
|
||||
|
||||
EventUpdatesManager.debounce<KafkatorioPacketData.MapChunkUpdate>(
|
||||
key,
|
||||
Type.MapChunkUpdate,
|
||||
data => data.isDeleted = true,
|
||||
0, // deletion is important - emit ASAP
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
script.on_event(defines.events.on_pre_chunk_deleted, (e: OnPreChunkDeletedEvent) => {
|
||||
log(`on_pre_chunk_deleted ${e.tick}`)
|
||||
MapChunkUpdate.handlePreChunkDeleted(e)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
import {Converters} from "./converters";
|
||||
import EventUpdatesManager from "../cache/EventDataCache";
|
||||
import {
|
||||
KafkatorioPacketData,
|
||||
PlayerUpdateKey
|
||||
} from "../../generated/kafkatorio-schema";
|
||||
import Type = KafkatorioPacketData.Type;
|
||||
import {ForceIndex, KafkatorioPacketData, PlayerUpdateKey} from "../../generated/kafkatorio-schema";
|
||||
import packetEmitter from "../PacketEmitter";
|
||||
import Type = KafkatorioPacketData.Type;
|
||||
|
||||
|
||||
type PlayerUpdater = (player: LuaPlayer, data: KafkatorioPacketData.PlayerUpdate) => void
|
||||
|
|
@ -36,7 +33,7 @@ function playerUpdateThrottle(
|
|||
const eventName = Converters.eventNameString(event.name)
|
||||
|
||||
EventUpdatesManager.throttle<KafkatorioPacketData.PlayerUpdate>(
|
||||
{index: playerIndex},
|
||||
{index: playerIndex },
|
||||
Type.PlayerUpdate,
|
||||
data => {
|
||||
const player = game.players[playerIndex]
|
||||
|
|
@ -88,7 +85,7 @@ script.on_event(
|
|||
data.characterUnitNumber = player.character?.unit_number ?? null
|
||||
data.chatColour = Converters.mapColour(player.chat_color)
|
||||
data.colour = Converters.mapColour(player.color)
|
||||
data.forceIndex = player.force.index
|
||||
data.forceIndex = player.force.index as ForceIndex
|
||||
data.name = player.name
|
||||
data.isShowOnMap = player.show_on_map
|
||||
data.isSpectator = player.spectator
|
||||
|
|
@ -139,8 +136,7 @@ script.on_event(
|
|||
if (e.cause != undefined) {
|
||||
data.diedCause = {
|
||||
unitNumber: e.cause.unit_number ?? null,
|
||||
name: e.cause.name,
|
||||
protoType: e.cause.type
|
||||
protoId: Converters.prototypeId(e.cause.type, e.cause.name),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -189,7 +185,7 @@ script.on_event(
|
|||
log(`on_pre_player_left_game ${event.tick} ${event.name}`)
|
||||
|
||||
const playerUpdateKey: PlayerUpdateKey = {
|
||||
index: event.player_index
|
||||
index: event.player_index ,
|
||||
}
|
||||
|
||||
const playerUpdate: KafkatorioPacketData.PlayerUpdate = {
|
||||
|
|
|
|||
|
|
@ -26,11 +26,11 @@ function prototypeUpdates(): FactorioPrototype[] {
|
|||
|
||||
function getMapTilePrototypes(): FactorioPrototype.MapTile[] {
|
||||
let tiles: FactorioPrototype.MapTile[] = []
|
||||
for (let [, tile] of game.tile_prototypes) {
|
||||
for (const [, tile] of game.tile_prototypes) {
|
||||
tiles.push({
|
||||
type: FactorioPrototype.Type.MapTile,
|
||||
|
||||
name: tile.name,
|
||||
protoId: Converters.prototypeId("tile", tile.name),
|
||||
order: tile.order,
|
||||
layer: tile.layer,
|
||||
collisionMasks: Converters.convertCollisionMaskToNames(tile.collision_mask),
|
||||
|
|
@ -44,7 +44,7 @@ function getMapTilePrototypes(): FactorioPrototype.MapTile[] {
|
|||
|
||||
// function getEntityPrototypes(): FactorioPrototype.MapTile[] {
|
||||
// let tiles: FactorioPrototype.MapTile[] = []
|
||||
// for (let [, tile] of game.tile_prototypes) {
|
||||
// for (const [, tile] of game.tile_prototypes) {
|
||||
// tiles.push({
|
||||
// type: FactorioPrototype.Type.MapTile,
|
||||
//
|
||||
|
|
|
|||
|
|
@ -1,9 +1,9 @@
|
|||
import {Queue} from "../queue/queue";
|
||||
import {isEventType} from "./eventTypeCheck";
|
||||
import {Converters} from "./converters";
|
||||
import {handleChunkGeneratedEvent} from "./mapChunkUpdates";
|
||||
import {handleSurfaceUpdate} from "./surfaceUpdates";
|
||||
import EventCacheService from "../cache/EventDataCache";
|
||||
import MapChunkUpdate from "./mapChunkUpdates";
|
||||
|
||||
|
||||
script.on_event(
|
||||
|
|
@ -35,7 +35,7 @@ script.on_event(
|
|||
let eName = Converters.eventNameString(event.name)
|
||||
|
||||
log(`[on_tick:${event.tick}] dequed event ${eName}, delay ${i}`)
|
||||
handleChunkGeneratedEvent(event, i)
|
||||
MapChunkUpdate.handleChunkGeneratedEvent(event, i)
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
// import {isEventType} from "./eventTypeCheck";
|
||||
// import {Converters} from "./converters";
|
||||
// import EventUpdatesManager, {EventUpdates} from "../cache/EventDataCache";
|
||||
// import {
|
||||
// KafkatorioPacketData,
|
||||
// MapChunkPosition,
|
||||
// PrototypeKey,
|
||||
// } from "../../generated/kafkatorio-schema";
|
||||
// import CacheKey = EventUpdates.PacketKey;
|
||||
// import Type = KafkatorioPacketData.Type;
|
||||
//
|
||||
//
|
||||
// export class ResourceEntityUpdatesHandler {
|
||||
//
|
||||
//
|
||||
// handleChunkGeneratedEvent(
|
||||
// event: OnChunkGeneratedEvent,
|
||||
// expirationDurationTicks?: uint,
|
||||
// ) {
|
||||
// const resourceEntities = this.getAreaResourceEntities(event.surface, event.area)
|
||||
//
|
||||
// mapTilesUpdateDebounce(
|
||||
// event.surface,
|
||||
// Converters.convertMapTablePosition(event.position),
|
||||
// tiles,
|
||||
// event,
|
||||
// undefined,
|
||||
// expirationDurationTicks,
|
||||
// )
|
||||
// }
|
||||
//
|
||||
//
|
||||
// private resourceEntityUpdatesThrottle(
|
||||
// surface: LuaSurface | undefined,
|
||||
// chunkPosition: MapChunkPosition,
|
||||
// tiles: TileRead[],
|
||||
// event: MapTileUpdateEvent,
|
||||
// updater?: MapChunkUpdater,
|
||||
// expirationDurationTicks?: uint,
|
||||
// ) {
|
||||
//
|
||||
// if (surface == undefined) {
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// const eventName = Converters.eventNameString(event.name)
|
||||
//
|
||||
// const key: KafkatorioPacketData.ResourceEntitiesUpdate["key"] = {
|
||||
// surfaceIndex: surface.index as SurfaceIndex,
|
||||
// chunkPosition: chunkPosition,
|
||||
// }
|
||||
//
|
||||
// EventUpdatesManager.throttle<KafkatorioPacketData.ResourceEntitiesUpdate>(
|
||||
// key,
|
||||
// Type.MapChunkUpdate,
|
||||
// data => {
|
||||
//
|
||||
// // tile dictionary update
|
||||
// if (data.tileDictionary == undefined) {
|
||||
// data.tileDictionary = {
|
||||
// tilesXY: {},
|
||||
// protos: {},
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// let protosCount: uint = table_size(data.tileDictionary.protos)
|
||||
// for (const tile of tiles) {
|
||||
// const tileName = tile.name as PrototypeIdMapTile
|
||||
// const protoKey = protosCount++ as PrototypeKey
|
||||
// data.tileDictionary.protos[tileName] ??= protoKey
|
||||
//
|
||||
// const xString = `${tile.position.x}`
|
||||
// const yString = `${tile.position.y}`
|
||||
//
|
||||
// data.tileDictionary.tilesXY[xString] ??= {}
|
||||
// data.tileDictionary.tilesXY[xString][yString] = protoKey
|
||||
// }
|
||||
//
|
||||
// // events count update
|
||||
// data.events ??= {}
|
||||
// data.events[eventName] ??= []
|
||||
// data.events[eventName].push(event.tick)
|
||||
//
|
||||
// // apply mutator
|
||||
// if (updater != undefined) {
|
||||
// updater(data)
|
||||
// }
|
||||
// },
|
||||
// expirationDurationTicks,
|
||||
// )
|
||||
// }
|
||||
//
|
||||
// }
|
||||
//
|
||||
//
|
||||
//
|
||||
// const ResourceEntityUpdates = new ResourceEntityUpdatesHandler()
|
||||
//
|
||||
// export default ResourceEntityUpdates
|
||||
|
|
@ -13,7 +13,7 @@ export function handleSurfaceUpdate(
|
|||
|
||||
name: surface.name,
|
||||
index: surface.index,
|
||||
daytime: surface.daytime
|
||||
daytime: surface.daytime,
|
||||
}
|
||||
|
||||
PacketEmitter.emitInstantPacket(update)
|
||||
|
|
|
|||
|
|
@ -6,7 +6,8 @@ export class KafkatorioSettingsConfig {
|
|||
|
||||
private static eventCacheExpirationDefaultSeconds: Record<KafkatorioKeyedPacketTypes, double> = {
|
||||
"kafkatorio.packet.keyed.PlayerUpdate": 0.5,
|
||||
"kafkatorio.packet.keyed.MapChunkUpdate": 30,
|
||||
"kafkatorio.packet.keyed.MapChunkTileUpdate": 30,
|
||||
"kafkatorio.packet.keyed.MapChunkEntityUpdate": 30,
|
||||
"kafkatorio.packet.keyed.EntityUpdate": 5,
|
||||
}
|
||||
|
||||
|
|
@ -21,7 +22,7 @@ export class KafkatorioSettingsConfig {
|
|||
public initialiseSettings(data: Data): void {
|
||||
for (const [type,] of pairs(KafkatorioSettingsConfig.eventCacheExpirationDefaultSeconds)) {
|
||||
data.extend(
|
||||
[ KafkatorioSettingsConfig.createDefaultCacheDuration(type)]
|
||||
[KafkatorioSettingsConfig.createDefaultCacheDuration(type)]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import {KafkatorioPacketData} from "../generated/kafkatorio-schema";
|
||||
|
||||
export type EventName = keyof typeof defines.events
|
||||
export type DefinedEventName = keyof typeof defines.events
|
||||
|
||||
|
||||
/** Get all keys of `TYPE` that are not nullable. */
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue